├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── SECURITY.md ├── dependabot.yml └── workflows │ ├── checks.yml │ └── mcp-server-release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README.md ├── commitlint.config.js └── modelcontextprotocol ├── .cursor └── rules │ ├── mcp-guidelines.mdc │ ├── project-structure.mdc │ ├── python.mdc │ └── tool-development-guide.mdc ├── .dockerignore ├── .env.template ├── .python-version ├── Dockerfile ├── README.md ├── client.py ├── docs └── LOCAL_BUILD.md ├── pyproject.toml ├── server.py ├── settings.py ├── tools ├── __init__.py ├── assets.py ├── dsl.py ├── lineage.py ├── models.py └── search.py ├── utils ├── constants.py └── search.py ├── uv.lock └── version.py /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at hello@atlan.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug or unexpected behavior in the agent toolkit 4 | title: '[BUG] ' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Python version and environment details 16 | 2. Command or code that triggered the bug 17 | 3. Expected output vs actual output 18 | 19 | **Environment Information** 20 | - Python version: [e.g. 3.9.0] 21 | - OS: [e.g. macOS, Linux, Windows] 22 | - Package versions: [e.g. python-dotenv==1.0.0, pydantic==2.0.0] 23 | 24 | **Error Message** 25 | ``` 26 | Paste any error messages or stack traces here 27 | ``` 28 | 29 | **Expected behavior** 30 | A clear and concise description of what you expected to happen. 31 | 32 | **Additional context** 33 | Add any other context about the problem here, such as: 34 | - Related configuration files 35 | - Relevant environment variables 36 | - Any workarounds you've tried 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation or Question 3 | about: Ask a question or request documentation improvements 4 | title: '[DOCS] ' 5 | labels: 'documentation' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What is your question or documentation request?** 11 | A clear and concise description of what you need help with or what documentation you'd like to see improved. 12 | 13 | **Additional context** 14 | Add any other context about your question or documentation request here, such as: 15 | - Related documentation you've already reviewed 16 | - Specific sections that need clarification 17 | - Examples or use cases you'd like to see documented 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature or enhancement for the agent toolkit 4 | title: '[FEATURE] ' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Technical Details** 17 | - Proposed API changes (if any) 18 | - Impact on existing functionality 19 | - Required dependencies or new packages 20 | 21 | **Describe alternatives you've considered** 22 | A clear and concise description of any alternative solutions or features you've considered. 23 | 24 | **Additional context** 25 | Add any other context about the feature request here, such as: 26 | - Use cases or scenarios 27 | - Related issues or pull requests 28 | - Implementation considerations 29 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Vulnerability Disclosure 2 | 3 | If you think you have found a potential security vulnerability, 4 | please open a [draft Security Advisory](https://github.com/atlanhq/agent-toolkit/security/advisories/new) 5 | via GitHub. We will coordinate verification and next steps through 6 | that secure medium. 7 | 8 | If English is not your first language, please try to describe the 9 | problem and its impact to the best of your ability. For greater detail, 10 | please use your native language and we will try our best to translate it 11 | using online services. 12 | 13 | Please also include the code you used to find the problem and the 14 | shortest amount of code necessary to reproduce it. 15 | 16 | Please do not disclose this to anyone else. We will retrieve a CVE 17 | identifier if necessary and give you full credit under whatever name or 18 | alias you provide. We will only request an identifier when we have a fix 19 | and can publish it in a release. 20 | 21 | We will respect your privacy and will only publicize your involvement if 22 | you grant us permission. 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/modelcontextprotocol" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 100 8 | allow: 9 | - dependency-type: "all" 10 | 11 | - package-ecosystem: "github-actions" 12 | directory: "/modelcontextprotocol" 13 | schedule: 14 | interval: daily 15 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | # Pre-commit-checks. This can be reused across all the applications. 2 | 3 | name: Pre-commit Checks 4 | on: 5 | workflow_call: 6 | pull_request: 7 | types: [ opened, synchronize, labeled, reopened ] 8 | branches: "main" 9 | 10 | jobs: 11 | pre-commit: 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 10 17 | steps: 18 | - uses: actions/checkout@v4 19 | #---------------------------------------------- 20 | # ----- install & configure Python + UV ----- 21 | #---------------------------------------------- 22 | - uses: actions/setup-python@v5 23 | with: 24 | python-version: '3.11' 25 | - name: Install UV 26 | uses: astral-sh/setup-uv@v5 27 | #---------------------------------------------- 28 | # ----- install dependencies & run pre-commit ----- 29 | #---------------------------------------------- 30 | - name: Install dependencies and run pre-commit 31 | run: | 32 | uv pip install --system pre-commit 33 | # Run pre-commit directly 34 | pre-commit run --all-files 35 | -------------------------------------------------------------------------------- /.github/workflows/mcp-server-release.yml: -------------------------------------------------------------------------------- 1 | name: MCP-Release 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | branches: 7 | - main 8 | 9 | jobs: 10 | prepare-release: 11 | # Only run when a PR with the "release" label is merged 12 | if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release') 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | outputs: 17 | version: ${{ steps.get_version.outputs.version }} 18 | should_release: ${{ steps.check_tag.outputs.exists == 'false' }} 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Get version 26 | id: get_version 27 | run: | 28 | VERSION=$(grep -m 1 "__version__" modelcontextprotocol/version.py | cut -d'"' -f2) 29 | echo "version=$VERSION" >> $GITHUB_OUTPUT 30 | echo "Found version: $VERSION" 31 | 32 | - name: Check if tag exists 33 | id: check_tag 34 | run: | 35 | TAG_NAME="v${{ steps.get_version.outputs.version }}" 36 | if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then 37 | echo "Tag $TAG_NAME already exists, stopping workflow" 38 | echo "exists=true" >> $GITHUB_OUTPUT 39 | else 40 | echo "Tag $TAG_NAME does not exist, continuing workflow" 41 | echo "exists=false" >> $GITHUB_OUTPUT 42 | fi 43 | 44 | - name: Generate changelog entry 45 | id: changelog 46 | if: steps.check_tag.outputs.exists == 'false' 47 | run: | 48 | set +e 49 | 50 | VERSION="${{ steps.get_version.outputs.version }}" 51 | RELEASE_DATE=$(date +"%Y-%m-%d") 52 | 53 | echo "Generating changelog for version $VERSION ($RELEASE_DATE)" 54 | 55 | # Get the previous version tag 56 | PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "") 57 | 58 | if [ -z "$PREV_TAG" ]; then 59 | # If no previous tag, get the first commit 60 | FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) 61 | RANGE="$FIRST_COMMIT..HEAD" 62 | echo "Using range from first commit to HEAD" 63 | else 64 | RANGE="$PREV_TAG..HEAD" 65 | echo "Using range from $PREV_TAG to HEAD" 66 | fi 67 | 68 | # Create temporary changelog entry for RELEASE_NOTES.md 69 | echo "## [$VERSION] - $RELEASE_DATE" > RELEASE_NOTES.md 70 | echo "" >> RELEASE_NOTES.md 71 | 72 | # Add features 73 | git log $RANGE --format="* %s (%h)" --grep="^feat" --perl-regexp --no-merges 2>/dev/null > features.txt || touch features.txt 74 | 75 | if [ -s features.txt ]; then 76 | echo "### Added" >> RELEASE_NOTES.md 77 | echo "" >> RELEASE_NOTES.md 78 | sed 's/^\* feat[[:space:]]*\([^:]*\):[[:space:]]*/* /' features.txt >> RELEASE_NOTES.md 79 | echo "" >> RELEASE_NOTES.md 80 | fi 81 | 82 | # Add fixes 83 | git log $RANGE --format="* %s (%h)" --grep="^fix" --perl-regexp --no-merges 2>/dev/null > fixes.txt || touch fixes.txt 84 | 85 | if [ -s fixes.txt ]; then 86 | echo "### Fixed" >> RELEASE_NOTES.md 87 | echo "" >> RELEASE_NOTES.md 88 | sed 's/^\* fix[[:space:]]*\([^:]*\):[[:space:]]*/* /' fixes.txt >> RELEASE_NOTES.md 89 | echo "" >> RELEASE_NOTES.md 90 | fi 91 | 92 | # Add other changes (excluding merge commits, chore, docs, style, refactor, test, ci) 93 | git log $RANGE --format="* %s (%h)" --no-merges 2>/dev/null | \ 94 | grep -v -E "^\* (feat|fix|chore|docs|style|refactor|test|ci)(\(.*\))?:" > others.txt || touch others.txt 95 | 96 | if [ -s others.txt ]; then 97 | echo "### Changed" >> RELEASE_NOTES.md 98 | echo "" >> RELEASE_NOTES.md 99 | cat others.txt >> RELEASE_NOTES.md 100 | echo "" >> RELEASE_NOTES.md 101 | fi 102 | 103 | # If no specific changes found, add a simple entry 104 | if [ ! -s features.txt ] && [ ! -s fixes.txt ] && [ ! -s others.txt ]; then 105 | echo "### Changed" >> RELEASE_NOTES.md 106 | echo "" >> RELEASE_NOTES.md 107 | echo "* Release version $VERSION" >> RELEASE_NOTES.md 108 | echo "" >> RELEASE_NOTES.md 109 | fi 110 | 111 | # Clean up temporary files 112 | rm -f features.txt fixes.txt others.txt 113 | 114 | echo "Release notes generated successfully" 115 | echo "================================" 116 | cat RELEASE_NOTES.md 117 | echo "================================" 118 | 119 | - name: Create Tag 120 | if: steps.check_tag.outputs.exists == 'false' 121 | run: | 122 | git tag v${{ steps.get_version.outputs.version }} 123 | git push --tags 124 | 125 | - name: Create GitHub Release 126 | if: steps.check_tag.outputs.exists == 'false' 127 | uses: softprops/action-gh-release@v2 128 | with: 129 | tag_name: v${{ steps.get_version.outputs.version }} 130 | body_path: RELEASE_NOTES.md 131 | token: ${{ secrets.GITHUB_TOKEN }} 132 | draft: false 133 | prerelease: false 134 | 135 | # Upload release notes for other jobs to use 136 | - name: Upload release notes 137 | if: steps.check_tag.outputs.exists == 'false' 138 | uses: actions/upload-artifact@v4 139 | with: 140 | name: release-notes 141 | path: RELEASE_NOTES.md 142 | retention-days: 1 143 | 144 | publish-pypi: 145 | needs: prepare-release 146 | if: needs.prepare-release.outputs.should_release == 'true' 147 | runs-on: ubuntu-latest 148 | permissions: 149 | contents: read 150 | steps: 151 | - name: Checkout 152 | uses: actions/checkout@v4 153 | with: 154 | ref: v${{ needs.prepare-release.outputs.version }} 155 | 156 | - name: Set up Python 157 | uses: actions/setup-python@v5 158 | with: 159 | python-version: '3.11' 160 | 161 | - name: Install build dependencies 162 | run: | 163 | python -m pip install --upgrade pip 164 | pip install build wheel twine 165 | 166 | - name: Build package 167 | run: | 168 | cd modelcontextprotocol 169 | python -m build 170 | 171 | - name: Publish to PyPI 172 | env: 173 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 174 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 175 | run: | 176 | cd modelcontextprotocol 177 | twine upload dist/* 178 | 179 | publish-docker: 180 | needs: prepare-release 181 | if: needs.prepare-release.outputs.should_release == 'true' 182 | runs-on: ubuntu-latest 183 | permissions: 184 | contents: read 185 | packages: write 186 | steps: 187 | - name: Checkout 188 | uses: actions/checkout@v4 189 | with: 190 | ref: v${{ needs.prepare-release.outputs.version }} 191 | 192 | - name: Set up QEMU for Cross-Platform Builds 193 | uses: docker/setup-qemu-action@v3 194 | 195 | - name: Set up Docker Buildx 196 | uses: docker/setup-buildx-action@v3 197 | 198 | - name: Login to GitHub Container Registry 199 | uses: docker/login-action@v3 200 | with: 201 | registry: ghcr.io 202 | username: ${{ github.actor }} 203 | password: ${{ secrets.GITHUB_TOKEN }} 204 | 205 | - name: Build and push Docker image 206 | uses: docker/build-push-action@v5 207 | with: 208 | context: ./modelcontextprotocol/ 209 | push: true 210 | tags: | 211 | ghcr.io/atlanhq/atlan-mcp-server:latest 212 | ghcr.io/atlanhq/atlan-mcp-server:${{ needs.prepare-release.outputs.version }} 213 | platforms: | 214 | linux/amd64 215 | linux/arm64 216 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | .idea/ 161 | .vscode/ 162 | 163 | .DS_Store 164 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: check-added-large-files 9 | - id: check-ast 10 | - id: check-json 11 | - id: check-merge-conflict 12 | - id: detect-private-key 13 | 14 | - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook 15 | rev: v9.11.0 16 | hooks: 17 | - id: commitlint 18 | stages: [commit-msg] 19 | additional_dependencies: ['@commitlint/config-conventional'] 20 | 21 | - repo: https://github.com/astral-sh/ruff-pre-commit 22 | rev: v0.3.0 23 | hooks: 24 | - id: ruff 25 | args: [--fix, --exit-non-zero-on-fix] 26 | - id: ruff-format 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.2.1] - 2025-05-24 9 | 10 | ### Added 11 | - Advanced search operators support in `search_assets` including `contains`, `between`, and case-insensitive comparisons 12 | - Default attributes for search results via `DEFAULT_SEARCH_ATTRIBUTES` constant with dynamic user-specified attribute support 13 | - Enhanced "some conditions" handling with support for advanced operators and case-insensitive logic 14 | - New search examples demonstrating OR logic for multiple type names and glossary term searches by specific attributes 15 | 16 | ### Changed 17 | - Integrated `SearchUtils` for centralized and consistent search result processing 18 | - Improved search API flexibility and precision with advanced query capabilities 19 | 20 | ### Fixed 21 | - Release workflow changelog generation issues that previously caused empty release notes 22 | - Improved commit range calculation and error handling in GitHub Actions workflow 23 | 24 | ## [0.2.0] - 2025-05-17 25 | 26 | ### Added 27 | - Support for new transport modes: streamable HTTP and SSE 28 | - MCP server executable script (`atlan-mcp-server`) 29 | - Improved Docker image with non-root user and security enhancements 30 | 31 | ### Changed 32 | - Made MCP server an installable package 33 | - Updated dependencies and bumped versions 34 | - Improved build process for faster Docker builds 35 | - Restructured release workflow for better isolation and PR-based releases 36 | 37 | ### Fixed 38 | - Various minor bugs and stability issues 39 | 40 | ### Documentation 41 | - Updated setup and usage instructions 42 | - Added more comprehensive examples 43 | 44 | 45 | ## [0.1.0] - 2024-05-05 46 | 47 | ### Added 48 | - Initial release of Atlan MCP Server 49 | - Basic functionality for integrating with Atlan 50 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file defines code owners for different parts of the repository 2 | # Code owners are automatically requested for review when someone opens a pull request 3 | # that modifies code that they own. 4 | 5 | # Default owners for everything in the repo 6 | * @Hk669 @firecast 7 | 8 | # Model Context Protocol specific files 9 | /modelcontextprotocol/ @Hk669 @firecast 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Atlan Pte Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Atlan Agent Toolkit 2 | 3 | This repository contains a collection of tools and protocols for interacting with Atlan services for AI agents. Each component is designed to provide specific functionality and can be used independently or together. 4 | 5 | ## Components 6 | 7 | ### [Model Context Protocol (MCP)](modelcontextprotocol/README.md) 8 | A protocol server that enables interaction with Atlan services through function calling. Provides tools for asset search, and retrieval using [pyatlan](https://developer.atlan.com/sdks/python/). 9 | 10 | 11 | ## Contributing Guidelines 12 | 13 | We welcome contributions to the Atlan Agent Toolkit! Please follow these guidelines when submitting pull requests: 14 | 15 | 1. **Create a New Branch:** 16 | - Create a new branch for your changes. 17 | - Use a descriptive name for the branch (e.g., `feature/add-new-tool`). 18 | 19 | 2. **Make Your Changes:** 20 | - Make your changes in the new branch. 21 | - Ensure your tools are well-defined and follow the MCP specification. 22 | 23 | 3. **Submit a Pull Request:** 24 | - Push your changes to your branch. 25 | - Create a pull request against the `main` branch. 26 | - Provide a clear description of the changes and any related issues. 27 | - Ensure the PR passes all CI checks before requesting a review. 28 | 29 | 4. **Code Quality:** 30 | - We use pre-commit hooks to maintain code quality. 31 | - Install pre-commit in your local environment: 32 | ```bash 33 | uv pip install pre-commit 34 | pre-commit install 35 | ``` 36 | - Pre-commit will automatically run checks before each commit, including: 37 | - Code formatting with Ruff 38 | - Trailing whitespace removal 39 | - End-of-file fixing 40 | - YAML and JSON validation 41 | - Other quality checks 42 | 43 | 5. **Environment Setup:** 44 | - This project uses UV for dependency management. 45 | - Refer to the [Model Context Protocol README](modelcontextprotocol/README.md) for setup instructions. 46 | - Python 3.11 or higher is required. 47 | 48 | 6. **Documentation:** 49 | - Update documentation to reflect your changes. 50 | - Add comments to your code where necessary. 51 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | [ 8 | 'feat', 9 | 'fix', 10 | 'docs', 11 | 'style', 12 | 'refactor', 13 | 'perf', 14 | 'test', 15 | 'build', 16 | 'ci', 17 | 'chore', 18 | 'revert' 19 | ] 20 | ], 21 | 'subject-case': [0], // Disabled to allow any case 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /modelcontextprotocol/.cursor/rules/mcp-guidelines.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: false 5 | --- 6 | You are an expert in Python, Model Context Protocol (MCP), and Atlan integration. 7 | 8 | Key Principles 9 | - Write concise, technical responses with accurate Python examples for Atlan integration. 10 | - Use modular, well-structured code with clear separation of concerns. 11 | - Implement proper error handling and logging for robust API communication. 12 | - Use descriptive variable names that reflect domain terminology. 13 | - Follow Atlan and Model Context Protocol best practices. 14 | - Create reusable utility functions for common operations. 15 | 16 | Python/MCP/Atlan Guidelines 17 | - Use FastMCP for server implementation with clear tool definitions. 18 | - Leverage PyAtlan's rich API for communicating with Atlan services. 19 | - Use type hints for all function signatures, especially for complex Atlan objects. 20 | - Implement proper error handling with appropriate logging. 21 | - Use environment variables for configuration using Pydantic's BaseSettings. 22 | - Structure code with client initialization, tool definitions, and execution handlers. 23 | 24 | Code Structure 25 | - Separate server configuration from tool implementations. 26 | - Create dedicated client factory functions for Atlan API interactions. 27 | - Implement consistent logging throughout the application. 28 | - Follow the pattern of defining tools with descriptive docstrings for automatic MCP tool generation. 29 | - Use type annotations compatible with both PyAtlan and MCP. 30 | 31 | Atlan-Specific Guidelines 32 | - Use PyAtlan classes ( eg Asset, Table, Column) for type checking and accessing model attributes. 33 | - Implement FluentSearch for complex asset queries with flexible conditions. 34 | - Use CompoundQuery for common filter patterns like active assets. 35 | - Support pagination for large result sets. 36 | - Handle proper error cases for Atlan API responses. 37 | - Implement rich filtering options (conditions, negative conditions, some conditions). 38 | - Support inclusion of specific attributes in results. 39 | 40 | Error Handling and Validation 41 | - Log all API requests and responses at appropriate levels. 42 | - Implement comprehensive error handling: 43 | - Catch and log exceptions from Atlan API calls. 44 | - Return empty lists or default values for failed operations. 45 | - Use descriptive error messages with contextual information. 46 | - Include troubleshooting information in logs. 47 | - Validate input parameters before constructing API requests. 48 | 49 | Execution Model 50 | - Use synchronous operations for Atlan API calls. 51 | - Implement appropriate request timeout settings. 52 | - Apply rate limiting for bulk operations. 53 | - Use pagination for large result sets. 54 | - Return well-structured responses that MCP can properly format. 55 | 56 | Search Optimization 57 | - Build search queries incrementally with appropriate logging. 58 | - Support various search patterns: equality, containment, pattern matching. 59 | - Implement efficient filtering strategies using PyAtlan's built-in operators. 60 | - Support a wide range of search conditions including date ranges. 61 | - Optimize complex queries with proper indexing strategies. 62 | 63 | DSL Query Handling 64 | - Validate and parse DSL JSON properly. 65 | - Provide examples for common DSL patterns. 66 | - Support various query structures like function_score and bool queries. 67 | - Return both results and aggregations. 68 | - Implement proper error handling for malformed DSL queries. 69 | 70 | Tools and Utility Functions 71 | - Create helper functions for common operations like: 72 | - Query building 73 | - Result formatting 74 | - Error handling 75 | - Attribute resolution 76 | - Implement consistent patterns for handling Atlan's typed attributes. 77 | 78 | Key Conventions 79 | 1. Use PyAtlan's fluent interface for building search queries. 80 | 2. Properly handle authentication and API errors. 81 | 3. Implement appropriate logging at multiple levels: 82 | - Debug for query construction details 83 | - Info for operation completion 84 | - Warning for non-critical issues 85 | - Error for failures 86 | 4. Follow MCP conventions for tool registration and execution. 87 | 5. Provide rich documentation for tools with examples. 88 | 89 | Dependencies 90 | - mcp[cli] for MCP server implementation 91 | - pyatlan for Atlan API integration 92 | - pydantic-settings for configuration management 93 | - logging for application logging 94 | -------------------------------------------------------------------------------- /modelcontextprotocol/.cursor/rules/project-structure.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | # Atlan MCP Server Project Structure 7 | 8 | This document outlines the recommended project structure for the Atlan MCP server. 9 | 10 | ## Directory Structure 11 | 12 | ``` 13 | modelcontextprotocol/ 14 | ├── .cursor/ # Cursor IDE specific files 15 | ├── .gitignore # Git ignore file (Assumed) 16 | ├── .python-version # Python version specification 17 | ├── client.py # Atlan client factory 18 | ├── pyproject.toml # Project metadata and dependencies 19 | ├── README.md # Project documentation 20 | ├── server.py # MCP server entry point 21 | ├── settings.py # Application settings 22 | ├── tools.py # Main tool registration/definition file 23 | ├── uv.lock # uv lock file 24 | └── tools/ # Directory for tool implementations/modules 25 | ``` 26 | 27 | ## Key Components 28 | 29 | ### server.py 30 | The main entry point for the MCP server. Registers and exposes tools defined in the `tools/` directory to interact with Atlan. 31 | 32 | ```python 33 | from mcp.server.fastmcp import FastMCP 34 | from tools import ( 35 | search_assets, 36 | get_assets_by_dsl, 37 | traverse_lineage, 38 | update_assets, 39 | UpdatableAttribute, 40 | CertificateStatus, 41 | UpdatableAsset, 42 | ) 43 | from pyatlan.model.fields.atlan_fields import AtlanField 44 | from typing import Optional, Dict, Any, List, Union, Type 45 | from pyatlan.model.assets import Asset 46 | from pyatlan.model.lineage import LineageDirection 47 | 48 | mcp = FastMCP("Atlan MCP", dependencies=["pyatlan"]) 49 | 50 | # Note: Docstrings and full parameter lists omitted for brevity. 51 | # Refer to the actual server.py and tools implementations for details. 52 | 53 | @mcp.tool() 54 | def search_assets_tool( 55 | conditions: Optional[Union[Dict[str, Any], str]] = None, 56 | # ... many other parameters ... 57 | ): 58 | """Advanced asset search using FluentSearch with flexible conditions.""" 59 | return search_assets(conditions=conditions, ...) 60 | 61 | @mcp.tool() 62 | def get_assets_by_dsl_tool(dsl_query: Union[str, Dict[str, Any]]): 63 | """Execute the search with the given DSL query.""" 64 | return get_assets_by_dsl(dsl_query) 65 | 66 | @mcp.tool() 67 | def traverse_lineage_tool( 68 | guid: str, 69 | direction: str, # UPSTREAM or DOWNSTREAM 70 | # ... other parameters ... 71 | ): 72 | """Traverse asset lineage in specified direction.""" 73 | return traverse_lineage(guid=guid, direction=direction, ...) 74 | 75 | @mcp.tool() 76 | def update_assets_tool( 77 | assets: Union[UpdatableAsset, List[UpdatableAsset]], 78 | attribute_name: UpdatableAttribute, 79 | attribute_values: List[Union[CertificateStatus, str]], 80 | ): 81 | """Update one or multiple assets with different values for the same attribute.""" 82 | return update_assets(assets=assets, attribute_name=attribute_name, attribute_values=attribute_values) 83 | ``` 84 | 85 | ### settings.py 86 | Configuration settings using Pydantic, loaded from environment variables or a `.env` file. 87 | 88 | ```python 89 | from pydantic_settings import BaseSettings 90 | 91 | class Settings(BaseSettings): 92 | """Application settings loaded from environment variables or .env file.""" 93 | ATLAN_BASE_URL: str 94 | ATLAN_API_KEY: str 95 | ATLAN_AGENT_ID: str 96 | ATLAN_AGENT: str = "atlan-mcp" 97 | 98 | @property 99 | def headers(self) -> dict: 100 | """Get the headers for API requests.""" 101 | return { 102 | "x-atlan-agent": self.ATLAN_AGENT, 103 | "x-atlan-agent-id": self.ATLAN_AGENT_ID, 104 | "x-atlan-client-origin": self.ATLAN_AGENT, 105 | } 106 | 107 | class Config: 108 | env_file = ".env" 109 | env_file_encoding = "utf-8" 110 | extra = "allow" 111 | case_sensitive = False 112 | ``` 113 | 114 | ### client.py 115 | Factory for creating and configuring the Atlan client using application settings. 116 | 117 | ```python 118 | import logging 119 | from pyatlan.client.atlan import AtlanClient 120 | from settings import Settings 121 | 122 | logger = logging.getLogger(__name__) 123 | 124 | def get_atlan_client() -> AtlanClient: 125 | """Create an Atlan client instance using settings loaded from environment.""" 126 | settings = Settings() 127 | try: 128 | client = AtlanClient( 129 | base_url=settings.ATLAN_BASE_URL, api_key=settings.ATLAN_API_KEY 130 | ) 131 | client.update_headers(settings.headers) 132 | logger.info("Atlan client created successfully") 133 | return client 134 | except Exception as e: 135 | logger.error(f"Error creating Atlan client: {e}") 136 | raise Exception(f"Error creating Atlan client: {e}") 137 | ``` 138 | 139 | ### tools/ Directory 140 | Contains the implementation of the different tools exposed by the server. 141 | - `assets.py`: Implements asset-related tools like search and update. 142 | - `dsl.py`: Implements the DSL query tool. 143 | - `lineage.py`: (Potentially) Implements lineage traversal tools. 144 | - `__init__.py`: Makes the directory a package and exports the tool functions. 145 | -------------------------------------------------------------------------------- /modelcontextprotocol/.cursor/rules/python.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | You are an AI assistant specialized in Python development. Your approach emphasizes: 7 | 8 | Clear project structure with separate directories for source code, docs, and config. 9 | 10 | Modular design with distinct files for models, services, controllers, and utilities. 11 | 12 | Configuration management using environment variables. 13 | 14 | Robust error handling and logging, including context capture. 15 | 16 | Detailed documentation using docstrings and README files. 17 | 18 | Dependency management via https://github.com/astral-sh/uv and virtual environments. 19 | 20 | Code style consistency using Ruff. 21 | 22 | CI/CD implementation with GitHub Actions or GitLab CI. 23 | 24 | AI-friendly coding practices: 25 | 26 | You provide code snippets and explanations tailored to these principles, optimizing for clarity and AI-assisted development. 27 | 28 | Follow the following rules: 29 | 30 | For any python file, be sure to ALWAYS add typing annotations to each function or class. Be sure to include return types when necessary. Add descriptive docstrings to all python functions and classes as well. Please use pep257 convention. Update existing docstrings if need be. 31 | 32 | Make sure you keep any comments that exist in a file. 33 | -------------------------------------------------------------------------------- /modelcontextprotocol/.cursor/rules/tool-development-guide.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | This guide outlines the core coding patterns for implementing tools in the Atlan MCP server. 7 | 8 | ## Tool Implementation Pattern 9 | 10 | 1. Define core function in tools.py 11 | 2. Register function as MCP tool in server.py 12 | 3. Use appropriate type hints for PyAtlan and MCP compatibility 13 | 14 | ## Core Function Template (tools.py) 15 | 16 | ```python 17 | def implement_atlan_tool( 18 | param1: str, 19 | param2: Optional[int] = None, 20 | param3: Optional[Dict[str, Any]] = None 21 | ) -> Any: 22 | """ 23 | Implement an Atlan tool with appropriate parameters. 24 | """ 25 | logger.info(f"Starting tool execution with parameters: param1={param1}") 26 | 27 | try: 28 | # Tool-specific implementation 29 | result = execute_atlan_operation(param1, param2, param3) 30 | 31 | logger.info(f"Tool execution completed successfully") 32 | return result 33 | except Exception as e: 34 | logger.error(f"Error executing tool: {str(e)}") 35 | logger.exception("Exception details:") 36 | return get_appropriate_default_value() 37 | ``` 38 | 39 | ## MCP Tool Registration Template (server.py) 40 | 41 | ```python 42 | @mcp.tool() 43 | def registered_tool_name( 44 | param1: str, 45 | param2: Optional[int] = None, 46 | param3: Optional[Dict[str, Any]] = None 47 | ) -> Any: 48 | """ 49 | Tool description with clear purpose. 50 | 51 | Args: 52 | param1: First parameter description 53 | param2: Second parameter description 54 | param3: Third parameter description (complex structure) 55 | 56 | Returns: 57 | Description of return value 58 | 59 | Example: 60 | registered_tool_name("value1", 42, {"key": "value"}) 61 | """ 62 | return implement_atlan_tool(param1, param2, param3) 63 | ``` 64 | 65 | ## Common Operation Patterns 66 | 67 | ### Asset Search Operation 68 | ```python 69 | def search_operation(criteria): 70 | search = FluentSearch() 71 | # Add filters based on criteria 72 | request = search.to_request() 73 | results = list(atlan_client.asset.search(request).current_page()) 74 | return results 75 | ``` 76 | 77 | ### Asset Retrieval Operation 78 | ```python 79 | def get_asset_operation(qualified_name, asset_type): 80 | try: 81 | asset = asset_type.get_by_qualified_name( 82 | qualified_name=qualified_name, 83 | min_ext_info=True, 84 | atlan_client=atlan_client 85 | ) 86 | return asset 87 | except Exception as e: 88 | logger.error(f"Failed to get asset: {e}") 89 | return None 90 | ``` 91 | 92 | ### Batch Processing Operation 93 | ```python 94 | def batch_operation(items, process_function): 95 | results = [] 96 | for item in items: 97 | try: 98 | result = process_function(item) 99 | results.append(result) 100 | except Exception as e: 101 | logger.warning(f"Error processing item {item}: {e}") 102 | results.append(None) 103 | return results 104 | ``` 105 | 106 | ### DSL Query Operation 107 | ```python 108 | def dsl_query_operation(dsl_query): 109 | try: 110 | dsl_dict = json.loads(dsl_query) 111 | index_request = IndexSearchRequest(dsl=DSL(**dsl_dict)) 112 | results = atlan_client.asset.search(index_request) 113 | return results 114 | except Exception as e: 115 | logger.error(f"DSL query error: {e}") 116 | return None 117 | ``` 118 | 119 | ## Parameter Validation Patterns 120 | 121 | ```python 122 | def validate_parameters(param, expected_type, allowed_values=None): 123 | if param is None: 124 | return False 125 | 126 | if not isinstance(param, expected_type): 127 | return False 128 | 129 | if allowed_values and param not in allowed_values: 130 | return False 131 | 132 | return True 133 | ``` 134 | 135 | ## Result Formatting Patterns 136 | 137 | ```python 138 | def format_asset_results(assets): 139 | return [ 140 | { 141 | "name": asset.name, 142 | "qualified_name": asset.qualified_name, 143 | "type": asset.type_name, 144 | "description": asset.description 145 | } 146 | for asset in assets if asset 147 | ] 148 | ``` 149 | 150 | ## Error Handling Pattern 151 | 152 | ```python 153 | try: 154 | # Attempt operation 155 | result = perform_operation() 156 | return result 157 | except ValueError as e: 158 | logger.error(f"Invalid input parameter: {str(e)}") 159 | return {"error": "Invalid input", "details": str(e)} 160 | except ConnectionError as e: 161 | logger.error(f"Connection to Atlan failed: {str(e)}") 162 | return {"error": "Connection failed", "details": str(e)} 163 | except Exception as e: 164 | logger.error(f"Operation failed: {str(e)}") 165 | logger.exception("Exception details:") 166 | return {"error": "Unknown error", "details": str(e)} 167 | ``` 168 | 169 | ## Common Type Patterns 170 | 171 | ```python 172 | from typing import Optional, Dict, Any, List, Union, Type, TypeVar, Callable, Tuple 173 | 174 | # Generic result type 175 | Result = Union[Dict[str, Any], List[Dict[str, Any]], None] 176 | 177 | # Function returning success status and result 178 | def operation_with_status() -> Tuple[bool, Optional[Result], Optional[str]]: 179 | try: 180 | result = perform_operation() 181 | return True, result, None 182 | except Exception as e: 183 | return False, None, str(e) 184 | ``` 185 | 186 | ## Logging Pattern 187 | 188 | ```python 189 | # Start of operation 190 | logger.info(f"Starting {operation_name} with {parameters}") 191 | 192 | # Debug information during operation 193 | logger.debug(f"Intermediate state: {some_variable}") 194 | 195 | # Operation completed 196 | logger.info(f"Operation {operation_name} completed with {result_summary}") 197 | 198 | # Error handling 199 | logger.error(f"Operation {operation_name} failed: {error_message}") 200 | logger.exception("Exception details:") 201 | ``` 202 | -------------------------------------------------------------------------------- /modelcontextprotocol/.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | .venv 3 | __pycache__ 4 | .cursor 5 | .git 6 | .gitignore 7 | .vscode 8 | .vscodeignore 9 | .idea 10 | .DS_Store 11 | .pytest_cache 12 | .ruff_cache 13 | .mypy_cache 14 | .ruff_cache 15 | -------------------------------------------------------------------------------- /modelcontextprotocol/.env.template: -------------------------------------------------------------------------------- 1 | ATLAN_BASE_URL=https://domain.atlan.com 2 | ATLAN_API_KEY=your_api_key 3 | ATLAN_AGENT_ID=your_agent_id 4 | -------------------------------------------------------------------------------- /modelcontextprotocol/.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /modelcontextprotocol/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a Python image with uv pre-installed 2 | FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder 3 | 4 | # Set environment variables for build 5 | ENV PYTHONDONTWRITEBYTECODE=1 \ 6 | PYTHONUNBUFFERED=1 \ 7 | PIP_NO_CACHE_DIR=1 8 | 9 | # Install the project into `/app` 10 | WORKDIR /app 11 | 12 | ADD . /app 13 | 14 | # Create a virtual environment and install dependencies 15 | RUN python -m venv /app/.venv 16 | ENV PATH="/app/.venv/bin:$PATH" 17 | RUN uv sync --no-cache-dir --no-dev --python /app/.venv/bin/python 18 | 19 | FROM python:3.12-slim-bookworm AS runtime 20 | 21 | RUN groupadd -r appuser && useradd -r -g appuser -m -d /home/appuser appuser 22 | 23 | WORKDIR /appuser 24 | 25 | COPY --from=builder --chown=appuser:appuser /app /appuser 26 | 27 | # Set the PATH to use the virtual environment 28 | ENV PATH="/appuser/.venv/bin:$PATH" 29 | 30 | ENV MCP_TRANSPORT="stdio" 31 | ENV MCP_HOST="0.0.0.0" 32 | ENV MCP_PORT="8000" 33 | ENV MCP_PATH="/" 34 | 35 | USER appuser 36 | 37 | ENTRYPOINT exec python server.py --transport "$MCP_TRANSPORT" --host "$MCP_HOST" --port "$MCP_PORT" --path "$MCP_PATH" 38 | -------------------------------------------------------------------------------- /modelcontextprotocol/README.md: -------------------------------------------------------------------------------- 1 | # Atlan MCP Server 2 | 3 | The Atlan [Model Context Protocol](https://modelcontextprotocol.io/introduction) server allows your AI agents to interact with Atlan services. 4 | 5 | ## Table of Contents 6 | 7 | - [Available Tools](#available-tools) 8 | - [Running the MCP server](#running-the-mcp-server) 9 | - [Base Requirement](#base-requirement) 10 | - [Python based MCP server (Local)](#python-based-mcp-server-local) 11 | - [Docker/Podman based MCP server hosting(Local)](#dockerpodman-based-mcp-server-hostinglocal) 12 | - [Using the MCP server](#using-the-mcp-server) 13 | - [Claude Desktop](#claude-desktop) 14 | - [Cursor](#cursor) 15 | - [MCP configurations](#mcp-configurations) 16 | - [Python (Local)](#python-local) 17 | - [Container (Local)](#container-local) 18 | - [Production Deployment](#production-deployment) 19 | - [MCP configuration](#mcp-configuration) 20 | - [Develop Locally](#develop-locally) 21 | - [Need Help?](#need-help) 22 | - [Troubleshooting](#troubleshooting) 23 | 24 | ## Available Tools 25 | 26 | | Tool | Description | 27 | | ------------------------- | ----------------------------------------------------------------- | 28 | | `search_assets` | Search for assets based on conditions | 29 | | `get_assets_by_dsl` | Retrieve assets using a DSL query | 30 | | `traverse_lineage` | Retrieve lineage for an asset | 31 | | `update_assets` | Update asset attributes (user description and certificate status) | 32 | 33 | ## Running the MCP server 34 | - There are 2 different ways to run the Atlan MCP server locally 35 | - Python (Local) - Run the server directly on your machine using Python 36 | - Docker/Podman (Local) - Run the server as a local container 37 | 38 | ### Base Requirement 39 | - Atlan API Key needed for any of the above deployment type you choose. To generate the API key, refer to the [Atlan documentation](https://ask.atlan.com/hc/en-us/articles/8312649180049-API-authentication). 40 | 41 | ### Python based MCP server (Local) 42 | 1. Install Python 43 | 44 | Mac installation 45 | ```sh 46 | # Install homebrew (if not already installed) 47 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 48 | 49 | # Add Homebrew to your PATH (if not already done) 50 | echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile 51 | eval "$(/opt/homebrew/bin/brew shellenv)" 52 | 53 | # Install Python 3.11 54 | brew install python@3.11 55 | 56 | # Verify installation 57 | python3 --version # Should show Python 3.11.x 58 | ``` 59 | 60 | 2. Install [uv](https://docs.astral.sh/uv/getting-started/installation/#standalone-installer) 61 | 62 | ```sh 63 | # Mac 64 | brew install uv 65 | 66 | # Verify installation 67 | uv --version 68 | ``` 69 | 70 | 3. Clone and set up the repository 71 | ```sh 72 | # Clone the repository 73 | git clone https://github.com/atlanhq/agent-toolkit.git 74 | cd agent-toolkit/modelcontextprotocol 75 | 76 | # Create and activate a virtual environment 77 | uv venv 78 | source .venv/bin/activate # On Mac/Linux 79 | 80 | # Install dependencies 81 | uv sync 82 | ``` 83 | 84 | ### Docker/Podman based MCP server hosting(Local) 85 | 1. Install via Docker and Docker Desktop 86 | ```sh 87 | # Mac 88 | # Download Docker Desktop from https://www.docker.com/products/docker-desktop 89 | # Follow the installation wizard 90 | 91 | # Verify installation in the terminal 92 | docker --version 93 | docker compose version 94 | 95 | ``` 96 | 97 | 2. Install via Docker CLI and Colima 98 | ```sh 99 | # Mac 100 | # Install Colima 101 | brew install colima 102 | 103 | # Start Colima 104 | colima start 105 | 106 | # Install Docker CLI 107 | brew install docker 108 | 109 | # Verify installation 110 | docker --version 111 | 112 | # Build the latest Atlan MCP server image 113 | git clone https://github.com/atlanhq/agent-toolkit.git 114 | cd agent-toolkit/modelcontextprotocol 115 | 116 | docker build . -t atlan-mcp-server:latest 117 | ``` 118 | 119 | ## Using the MCP server 120 | ### [Claude Desktop](https://claude.ai/download) 121 | 1. Open Claude Desktop 122 | 2. Go to Settings(Cmd + `,`) or click on Claude in the top left menu and select "Settings" 123 | 3. Navigate to the **Developer** tab in the settings panel 124 | 4. Click **Edit Config**. This will open up Finder and a file named `claude_desktop_config.json` highlighted. 125 | 5. Open this file in an IDE of your choice and add the below [MCP configuration](#mcp-configurations) based on the server deployment method you chose earlier 126 | 127 | 128 | ### Cursor 129 | 1. Download and install Cursor from [cursor.sh](https://cursor.sh) 130 | 2. Open Cursor and open the project you wish to add the MCP server to 131 | 3. Create a `.cursor` directory in the root of your workspace (if not present already) 132 | 4. Create a `mcp.json` file inside the `.cursor` directory 133 | 5. Add the [MCP configuration](#mcp-configurations) to `mcp.json` based on the server deployment method you chose earlier 134 | 135 | ## MCP configurations 136 | ### Python (Local) 137 | ```json 138 | { 139 | "mcpServers": { 140 | "Atlan MCP": { 141 | "command": "uv", 142 | "args": [ 143 | "run", 144 | "/path/to/your/agent-toolkit/modelcontextprotocol/.venv/bin/atlan-mcp-server" 145 | ], 146 | "env": { 147 | "ATLAN_API_KEY": "your_api_key", 148 | "ATLAN_BASE_URL": "https://your-instance.atlan.com", 149 | "ATLAN_AGENT_ID": "your_agent_id" 150 | } 151 | } 152 | } 153 | } 154 | ``` 155 | **Note**: 156 | - Make sure to replace `/path/to/your/agent-toolkit` with the actual path to your cloned repository 157 | - Replace `your_api_key`, `your_instance`, and `your_agent_id` with your actual Atlan API key, instance URL, and agent ID(optional) respectively 158 | 159 | ### Container (Local) 160 | ```json 161 | { 162 | "mcpServers": { 163 | "atlan": { 164 | "command": "docker", 165 | "args": [ 166 | "run", 167 | "-i", 168 | "--rm", 169 | "-e", 170 | "ATLAN_API_KEY=your_api_key", 171 | "-e", 172 | "ATLAN_BASE_URL=https://your-instance.atlan.com", 173 | "-e", 174 | "ATLAN_AGENT_ID=your_agent_id", 175 | "atlan-mcp-server:latest" 176 | ] 177 | } 178 | } 179 | } 180 | ``` 181 | **Note**: 182 | - Make sure to replace `your_api_key`, `your_instance`, and `your_agent_id` with your actual Atlan API key, instance URL, and agent ID(optional) respectively 183 | 184 | 185 | ## Production Deployment 186 | - Host the Atlan MCP container image on the cloud/platform of your choice 187 | - Make sure you add all the required environment variables 188 | - Make sure you start the server in the SSE transport mode `-e MCP_TRANSPORT=sse` 189 | 190 | ### MCP configuration 191 | Even though Claude Desktop/Cursor don't yet support remote MCP clients, you can use the [mcp-remote](https://www.npmjs.com/package/mcp-remote) local proxy to connect it to your remote MCP server. 192 | This lets you to test what an interaction with your remote MCP server will be like with a real-world MCP client. 193 | ```json 194 | { 195 | "mcpServers": { 196 | "math": { 197 | "command": "npx", 198 | "args": ["mcp-remote", "https://hosted-domain"] 199 | } 200 | } 201 | } 202 | ``` 203 | 204 | ## Develop Locally 205 | Want to develop locally? Check out our [Local Build](./docs/LOCAL_BUILD.md) Guide for a step-by-step walkthrough! 206 | 207 | ## Need Help? 208 | - Reach out to support@atlan.com for any questions or feedback 209 | - You can also directly create a [GitHub issue](https://github.com/atlanhq/agent-toolkit/issues) and we will answer it for you 210 | 211 | ## Troubleshooting 212 | 1. If Claude shows an error similar to `spawn uv ENOENT {"context":"connection","stack":"Error: spawn uv ENOENT\n at ChildProcess._handle.onexit`, it is most likely [this](https://github.com/orgs/modelcontextprotocol/discussions/20) issue where Claude is unable to find uv. To fix it: 213 | - Make sure uv is installed and available in your PATH 214 | - Run `which uv` to verify the installation path 215 | - Update Claude's configuration to point to the exact uv path by running `whereis uv` and use that path 216 | -------------------------------------------------------------------------------- /modelcontextprotocol/client.py: -------------------------------------------------------------------------------- 1 | """Client factory for Atlan.""" 2 | 3 | import logging 4 | 5 | from pyatlan.client.atlan import AtlanClient 6 | from settings import Settings 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def get_atlan_client() -> AtlanClient: 12 | """Create an Atlan client instance using settings loaded from environment.""" 13 | settings = Settings() 14 | 15 | try: 16 | client = AtlanClient( 17 | base_url=settings.ATLAN_BASE_URL, api_key=settings.ATLAN_API_KEY 18 | ) 19 | client.update_headers(settings.headers) 20 | logger.info("Atlan client created successfully") 21 | return client 22 | except Exception as e: 23 | logger.error(f"Error creating Atlan client: {e}") 24 | raise Exception(f"Error creating Atlan client: {e}") 25 | -------------------------------------------------------------------------------- /modelcontextprotocol/docs/LOCAL_BUILD.md: -------------------------------------------------------------------------------- 1 | # Local Build 2 | 3 | 1. Clone the repository: 4 | ```bash 5 | git clone https://github.com/atlanhq/agent-toolkit.git 6 | cd agent-toolkit 7 | ``` 8 | 9 | 2. Install UV package manager: 10 | For macOS: 11 | ```bash 12 | # Using Homebrew 13 | brew install uv 14 | ``` 15 | 16 | For more installation options and detailed instructions, refer to the [official UV documentation](https://docs.astral.sh/uv/getting-started/installation/). 17 | 18 | 3. Install dependencies: 19 | > python version should be >= 3.11 20 | ```bash 21 | cd modelcontextprotocol 22 | uv sync 23 | ``` 24 | 25 | 4. Configure Atlan credentials: 26 | 27 | a. Using a .env file: 28 | Create a `.env` file in the root directory (or copy the `.env.template` file and rename it to `.env`) with the following content: 29 | ``` 30 | ATLAN_BASE_URL=https://your-instance.atlan.com 31 | ATLAN_API_KEY=your_api_key 32 | ATLAN_AGENT_ID=your_agent_id 33 | ``` 34 | 35 | **Note: `ATLAN_AGENT_ID` is optional but recommended. It will be used to identify which Agent is making the request on Atlan UI** 36 | 37 | To generate the API key, refer to the [Atlan documentation](https://ask.atlan.com/hc/en-us/articles/8312649180049-API-authentication). 38 | 39 | 5. Run the server: 40 | ```bash 41 | uv run .venv/bin/atlan-mcp-server 42 | ``` 43 | 44 | 6. (For debugging) Run the server with MCP inspector: 45 | ```bash 46 | uv run mcp dev server.py 47 | ``` 48 | -------------------------------------------------------------------------------- /modelcontextprotocol/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "atlan-mcp-server" 3 | dynamic = ["version"] 4 | description = "Atlan Model Context Protocol server for interacting with Atlan services" 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | license = { text = "MIT" } 8 | authors = [ 9 | {name = "AtlanHQ", email = "engineering@atlan.com"} 10 | ] 11 | classifiers = [ 12 | "Programming Language :: Python :: 3", 13 | "Programming Language :: Python :: 3.11", 14 | "License :: OSI Approved :: MIT License", 15 | "Operating System :: OS Independent", 16 | ] 17 | 18 | dependencies = [ 19 | "fastmcp>=2.3.4", 20 | "pyatlan>=6.0.1", 21 | ] 22 | 23 | [project.scripts] 24 | atlan-mcp-server = "server:main" 25 | 26 | [project.urls] 27 | "Homepage" = "https://github.com/atlanhq/agent-toolkit" 28 | "Documentation" = "https://ask.atlan.com/hc/en-us/articles/12525731740175-How-to-implement-the-Atlan-MCP-server" 29 | "Bug Tracker" = "https://github.com/atlanhq/agent-toolkit/issues" 30 | "Source" = "https://github.com/atlanhq/agent-toolkit.git" 31 | "Changelog" = "https://github.com/atlanhq/agent-toolkit/blob/main/CHANGELOG.md" 32 | 33 | [tool.hatch.version] 34 | path = "version.py" 35 | 36 | [tool.hatch.build.targets.wheel] 37 | packages = ["."] 38 | 39 | [build-system] 40 | requires = ["hatchling"] 41 | build-backend = "hatchling.build" 42 | -------------------------------------------------------------------------------- /modelcontextprotocol/server.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from fastmcp import FastMCP 3 | from tools import ( 4 | search_assets, 5 | get_assets_by_dsl, 6 | traverse_lineage, 7 | update_assets, 8 | UpdatableAttribute, 9 | CertificateStatus, 10 | UpdatableAsset, 11 | ) 12 | from pyatlan.model.lineage import LineageDirection 13 | 14 | mcp = FastMCP("Atlan MCP Server", dependencies=["pyatlan", "fastmcp"]) 15 | 16 | 17 | @mcp.tool() 18 | def search_assets_tool( 19 | conditions=None, 20 | negative_conditions=None, 21 | some_conditions=None, 22 | min_somes=1, 23 | include_attributes=None, 24 | asset_type=None, 25 | include_archived=False, 26 | limit=10, 27 | offset=0, 28 | sort_by=None, 29 | sort_order="ASC", 30 | connection_qualified_name=None, 31 | tags=None, 32 | directly_tagged=True, 33 | domain_guids=None, 34 | date_range=None, 35 | guids=None, 36 | ): 37 | """ 38 | Advanced asset search using FluentSearch with flexible conditions. 39 | 40 | Args: 41 | conditions (Dict[str, Any], optional): Dictionary of attribute conditions to match. 42 | Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}} 43 | negative_conditions (Dict[str, Any], optional): Dictionary of attribute conditions to exclude. 44 | Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}} 45 | some_conditions (Dict[str, Any], optional): Conditions for where_some() queries that require min_somes of them to match. 46 | Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}} 47 | min_somes (int): Minimum number of some_conditions that must match. Defaults to 1. 48 | include_attributes (List[Union[str, AtlanField]], optional): List of specific attributes to include in results. 49 | Can be string attribute names or AtlanField objects. 50 | asset_type (Union[Type[Asset], str], optional): Type of asset to search for. 51 | Either a class (e.g., Table, Column) or a string type name (e.g., "Table", "Column") 52 | include_archived (bool): Whether to include archived assets. Defaults to False. 53 | limit (int, optional): Maximum number of results to return. Defaults to 10. 54 | offset (int, optional): Offset for pagination. Defaults to 0. 55 | sort_by (str, optional): Attribute to sort by. Defaults to None. 56 | sort_order (str, optional): Sort order, "ASC" or "DESC". Defaults to "ASC". 57 | connection_qualified_name (str, optional): Connection qualified name to filter by. 58 | tags (List[str], optional): List of tags to filter by. 59 | directly_tagged (bool): Whether to filter for directly tagged assets only. Defaults to True. 60 | domain_guids (List[str], optional): List of domain GUIDs to filter by. 61 | date_range (Dict[str, Dict[str, Any]], optional): Date range filters. 62 | Format: {"attribute_name": {"gte": start_timestamp, "lte": end_timestamp}} 63 | guids (List[str], optional): List of asset GUIDs to filter by. 64 | 65 | Returns: 66 | List[Asset]: List of assets matching the search criteria 67 | 68 | Raises: 69 | Exception: If there's an error executing the search 70 | 71 | Examples: 72 | # Search for verified tables 73 | tables = search_assets( 74 | asset_type="Table", 75 | conditions={"certificate_status": CertificateStatus.VERIFIED.value} 76 | ) 77 | 78 | # Search for assets missing descriptions 79 | missing_desc = search_assets( 80 | negative_conditions={ 81 | "description": "has_any_value", 82 | "user_description": "has_any_value" 83 | }, 84 | include_attributes=["owner_users", "owner_groups"] 85 | ) 86 | 87 | # Search for columns with specific certificate status 88 | columns = search_assets( 89 | asset_type="Column", 90 | some_conditions={ 91 | "certificate_status": [CertificateStatus.DRAFT.value, CertificateStatus.VERIFIED.value] 92 | }, 93 | tags=["PRD"], 94 | conditions={"created_by": "username"}, 95 | date_range={"create_time": {"gte": 1641034800000, "lte": 1672570800000}} 96 | ) 97 | # Search for assets with a specific search text 98 | assets = search_assets( 99 | conditions = { 100 | "name": { 101 | "operator": "match", 102 | "value": "search_text" 103 | }, 104 | "description": { 105 | "operator": "match", 106 | "value": "search_text" 107 | } 108 | } 109 | ) 110 | 111 | 112 | # Search for assets using advanced operators 113 | assets = search_assets( 114 | conditions={ 115 | "name": { 116 | "operator": "startswith", 117 | "value": "prefix_", 118 | "case_insensitive": True 119 | }, 120 | "description": { 121 | "operator": "contains", 122 | "value": "important data", 123 | "case_insensitive": True 124 | }, 125 | "create_time": { 126 | "operator": "between", 127 | "value": [1640995200000, 1643673600000] 128 | } 129 | } 130 | ) 131 | 132 | # Search for assets with multiple type names (OR logic) 133 | assets = search_assets( 134 | conditions={ 135 | "type_name": ["Table", "Column", "View"] # Uses .within() for OR logic 136 | } 137 | ) 138 | 139 | # Search for assets with compliant business policy 140 | assets = search_assets( 141 | conditions={ 142 | "asset_policy_guids": "business_policy_guid" 143 | }, 144 | include_attributes=["asset_policy_guids"] 145 | ) 146 | 147 | # Search for assets with non compliant business policy 148 | assets = search_assets( 149 | conditions={ 150 | "non_compliant_asset_policy_guids": "business_policy_guid" 151 | }, 152 | include_attributes=["non_compliant_asset_policy_guids"] 153 | ) 154 | 155 | # get non compliant business policies for an asset 156 | assets = search_assets( 157 | conditions={ 158 | "name": "has_any_value", 159 | "displayName": "has_any_value", 160 | "guid": "has_any_value" 161 | }, 162 | include_attributes=["non_compliant_asset_policy_guids"] 163 | ) 164 | 165 | # get compliant business policies for an asset 166 | assets = search_assets( 167 | conditions={ 168 | "name": "has_any_value", 169 | "displayName": "has_any_value", 170 | "guid": "has_any_value" 171 | }, 172 | include_attributes=["asset_policy_guids"] 173 | ) 174 | 175 | # get incident for a business policy 176 | assets = search_assets( 177 | conditions={ 178 | "asset_type": "BusinessPolicyIncident", 179 | "business_policy_incident_related_policy_guids": "business_policy_guid" 180 | }, 181 | some_conditions={ 182 | "certificate_status": [CertificateStatus.DRAFT.value, CertificateStatus.VERIFIED.value] 183 | } 184 | ) 185 | 186 | # Search for glossary terms by name and status 187 | glossary_terms = search_assets( 188 | asset_type="AtlasGlossaryTerm", 189 | conditions={ 190 | "certificate_status": CertificateStatus.VERIFIED.value, 191 | "name": { 192 | "operator": "contains", 193 | "value": "customer", 194 | "case_insensitive": True 195 | } 196 | }, 197 | include_attributes=["categories"] 198 | ) 199 | 200 | """ 201 | return search_assets( 202 | conditions, 203 | negative_conditions, 204 | some_conditions, 205 | min_somes, 206 | include_attributes, 207 | asset_type, 208 | include_archived, 209 | limit, 210 | offset, 211 | sort_by, 212 | sort_order, 213 | connection_qualified_name, 214 | tags, 215 | directly_tagged, 216 | domain_guids, 217 | date_range, 218 | guids, 219 | ) 220 | 221 | 222 | @mcp.tool() 223 | def get_assets_by_dsl_tool(dsl_query): 224 | """ 225 | Execute the search with the given query 226 | dsl_query : Union[str, Dict[str, Any]] (required): 227 | The DSL query used to search the index. 228 | 229 | Example: 230 | dsl_query = '''{ 231 | "query": { 232 | "function_score": { 233 | "boost_mode": "sum", 234 | "functions": [ 235 | {"filter": {"match": {"starredBy": "john.doe"}}, "weight": 10}, 236 | {"filter": {"match": {"certificateStatus": "VERIFIED"}}, "weight": 15}, 237 | {"filter": {"match": {"certificateStatus": "DRAFT"}}, "weight": 10}, 238 | {"filter": {"bool": {"must_not": [{"exists": {"field": "certificateStatus"}}]}}, "weight": 8}, 239 | {"filter": {"bool": {"must_not": [{"terms": {"__typeName.keyword": ["Process", "DbtProcess"]}}]}}, "weight": 20} 240 | ], 241 | "query": { 242 | "bool": { 243 | "filter": [ 244 | { 245 | "bool": { 246 | "minimum_should_match": 1, 247 | "must": [ 248 | {"bool": {"should": [{"terms": {"certificateStatus": ["VERIFIED"]}}]}}, 249 | {"term": {"__state": "ACTIVE"}} 250 | ], 251 | "must_not": [ 252 | {"term": {"isPartial": "true"}}, 253 | {"terms": {"__typeName.keyword": ["Procedure", "DbtColumnProcess", "BIProcess", "MatillionComponent", "SnowflakeTag", "DbtTag", "BigqueryTag", "AIApplication", "AIModel"]}}, 254 | {"terms": {"__typeName.keyword": ["MCIncident", "AnomaloCheck"]}} 255 | ], 256 | "should": [ 257 | {"terms": {"__typeName.keyword": ["Query", "Collection", "AtlasGlossary", "AtlasGlossaryCategory", "AtlasGlossaryTerm", "Connection", "File"]}}, 258 | ] 259 | } 260 | } 261 | ] 262 | }, 263 | "score_mode": "sum" 264 | }, 265 | "score_mode": "sum" 266 | } 267 | }, 268 | "post_filter": { 269 | "bool": { 270 | "filter": [ 271 | { 272 | "bool": { 273 | "must": [{"terms": {"__typeName.keyword": ["Table", "Column"]}}], 274 | "must_not": [{"exists": {"field": "termType"}}] 275 | } 276 | } 277 | ] 278 | }, 279 | "sort": [ 280 | {"_score": {"order": "desc"}}, 281 | {"popularityScore": {"order": "desc"}}, 282 | {"starredCount": {"order": "desc"}}, 283 | {"name.keyword": {"order": "asc"}} 284 | ], 285 | "track_total_hits": true, 286 | "size": 10, 287 | "include_meta": false 288 | }''' 289 | response = get_assets_by_dsl(dsl_query) 290 | """ 291 | return get_assets_by_dsl(dsl_query) 292 | 293 | 294 | @mcp.tool() 295 | def traverse_lineage_tool( 296 | guid, 297 | direction, 298 | depth=1000000, 299 | size=10, 300 | immediate_neighbors=True, 301 | ): 302 | """ 303 | Traverse asset lineage in specified direction. 304 | 305 | Args: 306 | guid (str): GUID of the starting asset 307 | direction (str): Direction to traverse ("UPSTREAM" or "DOWNSTREAM") 308 | depth (int, optional): Maximum depth to traverse. Defaults to 1000000. 309 | size (int, optional): Maximum number of results to return. Defaults to 10. 310 | immediate_neighbors (bool, optional): Only return immediate neighbors. Defaults to True. 311 | 312 | Returns: 313 | Dict[str, Any]: Dictionary containing: 314 | - assets: List of assets in the lineage 315 | - references: List of dictionaries containing: 316 | - source_guid: GUID of the source asset 317 | - target_guid: GUID of the target asset 318 | - direction: Direction of the reference (upstream/downstream) 319 | 320 | Example: 321 | # Get lineage with specific depth and size 322 | lineage = traverse_lineage_tool( 323 | guid="asset-guid-here", 324 | direction="DOWNSTREAM", 325 | depth=1000000, 326 | size=10 327 | ) 328 | 329 | # Access assets and their references 330 | for asset in lineage["assets"]: 331 | print(f"Asset: {asset.guid}") 332 | 333 | for ref in lineage["references"]: 334 | print(f"Reference: {ref['source_guid']} -> {ref['target_guid']}") 335 | """ 336 | try: 337 | direction_enum = LineageDirection[direction.upper()] 338 | except KeyError: 339 | raise ValueError( 340 | f"Invalid direction: {direction}. Must be either 'UPSTREAM' or 'DOWNSTREAM'" 341 | ) 342 | 343 | return traverse_lineage( 344 | guid=str(guid), 345 | direction=direction_enum, 346 | depth=int(depth), 347 | size=int(size), 348 | immediate_neighbors=bool(immediate_neighbors), 349 | ) 350 | 351 | 352 | @mcp.tool() 353 | def update_assets_tool( 354 | assets, 355 | attribute_name, 356 | attribute_values, 357 | ): 358 | """ 359 | Update one or multiple assets with different values for the same attribute. 360 | 361 | Args: 362 | assets (Union[Dict[str, Any], List[Dict[str, Any]]]): Asset(s) to update. 363 | Can be a single UpdatableAsset or a list of UpdatableAsset objects. 364 | attribute_name (str): Name of the attribute to update. 365 | Only "user_description" and "certificate_status" are supported. 366 | attribute_values (List[str]): List of values to set for the attribute. 367 | For certificateStatus, only "VERIFIED", "DRAFT", or "DEPRECATED" are allowed. 368 | 369 | Returns: 370 | Dict[str, Any]: Dictionary containing: 371 | - updated_count: Number of assets successfully updated 372 | - errors: List of any errors encountered 373 | 374 | Examples: 375 | # Update certificate status for a single asset 376 | result = update_assets_tool( 377 | assets={ 378 | "guid": "asset-guid-here", 379 | "name": "Asset Name", 380 | "type_name": "Asset Type Name", 381 | "qualified_name": "Asset Qualified Name" 382 | }, 383 | attribute_name="certificate_status", 384 | attribute_values=["VERIFIED"] 385 | ) 386 | 387 | # Update user description for multiple assets 388 | result = update_assets_tool( 389 | assets=[ 390 | { 391 | "guid": "asset-guid-1", 392 | "name": "Asset Name 1", 393 | "type_name": "Asset Type Name 1", 394 | "qualified_name": "Asset Qualified Name 1" 395 | }, 396 | { 397 | "guid": "asset-guid-2", 398 | "name": "Asset Name 2", 399 | "type_name": "Asset Type Name 2", 400 | "qualified_name": "Asset Qualified Name 2" 401 | } 402 | ], 403 | attribute_name="user_description", 404 | attribute_values=[ 405 | "New description for asset 1", "New description for asset 2" 406 | ] 407 | ) 408 | 409 | """ 410 | try: 411 | # Convert string attribute name to enum 412 | attr_enum = UpdatableAttribute(attribute_name) 413 | 414 | # For certificate status, validate and convert values to enum 415 | if attr_enum == UpdatableAttribute.CERTIFICATE_STATUS: 416 | attribute_values = [CertificateStatus(val) for val in attribute_values] 417 | 418 | # Convert assets to UpdatableAsset objects 419 | if isinstance(assets, dict): 420 | updatable_assets = [UpdatableAsset(**assets)] 421 | elif isinstance(assets, list): 422 | updatable_assets = [UpdatableAsset(**asset) for asset in assets] 423 | else: 424 | raise ValueError("Assets must be a dictionary or a list of dictionaries") 425 | 426 | return update_assets( 427 | updatable_assets=updatable_assets, 428 | attribute_name=attr_enum, 429 | attribute_values=attribute_values, 430 | ) 431 | except ValueError as e: 432 | return {"updated_count": 0, "errors": [str(e)]} 433 | 434 | 435 | def main(): 436 | mcp.run() 437 | 438 | 439 | if __name__ == "__main__": 440 | parser = argparse.ArgumentParser(description="Atlan MCP Server") 441 | parser.add_argument( 442 | "--transport", 443 | type=str, 444 | default="stdio", 445 | choices=["stdio", "sse", "streamable-http"], 446 | help="Transport protocol (stdio/sse/streamable-http)", 447 | ) 448 | parser.add_argument( 449 | "--host", type=str, default="0.0.0.0", help="Host to run the server on" 450 | ) 451 | parser.add_argument( 452 | "--port", type=int, default=8000, help="Port to run the server on" 453 | ) 454 | parser.add_argument( 455 | "--path", type=str, default="/", help="Path of the streamable HTTP server" 456 | ) 457 | args = parser.parse_args() 458 | 459 | kwargs = {"transport": args.transport} 460 | if args.transport == "streamable-http" or args.transport == "sse": 461 | kwargs = { 462 | "transport": args.transport, 463 | "host": args.host, 464 | "port": args.port, 465 | "path": args.path, 466 | } 467 | # Run the server with the specified transport and host/port/path 468 | mcp.run(**kwargs) 469 | -------------------------------------------------------------------------------- /modelcontextprotocol/settings.py: -------------------------------------------------------------------------------- 1 | """Configuration settings for the application.""" 2 | 3 | from pydantic_settings import BaseSettings 4 | 5 | 6 | class Settings(BaseSettings): 7 | """Application settings loaded from environment variables or .env file.""" 8 | 9 | ATLAN_BASE_URL: str 10 | ATLAN_API_KEY: str 11 | ATLAN_AGENT_ID: str = "NA" 12 | ATLAN_AGENT: str = "atlan-mcp" 13 | ATLAN_MCP_USER_AGENT: str = "Atlan MCP Server 0.0.1" 14 | 15 | @property 16 | def headers(self) -> dict: 17 | """Get the headers for API requests.""" 18 | return { 19 | "User-Agent": self.ATLAN_MCP_USER_AGENT, 20 | "X-Atlan-Agent": self.ATLAN_AGENT, 21 | "X-Atlan-Agent-Id": self.ATLAN_AGENT_ID, 22 | "X-Atlan-Client-Origin": self.ATLAN_AGENT, 23 | } 24 | 25 | class Config: 26 | env_file = ".env" 27 | env_file_encoding = "utf-8" 28 | extra = "allow" 29 | # Allow case-insensitive environment variables 30 | case_sensitive = False 31 | -------------------------------------------------------------------------------- /modelcontextprotocol/tools/__init__.py: -------------------------------------------------------------------------------- 1 | from .search import search_assets 2 | from .dsl import get_assets_by_dsl 3 | from .lineage import traverse_lineage 4 | from .assets import update_assets 5 | from .models import CertificateStatus, UpdatableAttribute, UpdatableAsset 6 | 7 | __all__ = [ 8 | "search_assets", 9 | "get_assets_by_dsl", 10 | "traverse_lineage", 11 | "update_assets", 12 | "CertificateStatus", 13 | "UpdatableAttribute", 14 | "UpdatableAsset", 15 | ] 16 | -------------------------------------------------------------------------------- /modelcontextprotocol/tools/assets.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import List, Union, Dict, Any 3 | 4 | from client import get_atlan_client 5 | from .models import UpdatableAsset, UpdatableAttribute, CertificateStatus 6 | 7 | # Configure logging 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def update_assets( 12 | updatable_assets: Union[UpdatableAsset, List[UpdatableAsset]], 13 | attribute_name: UpdatableAttribute, 14 | attribute_values: List[Union[str, CertificateStatus]], 15 | ) -> Dict[str, Any]: 16 | """ 17 | Update one or multiple assets with different values for the same attribute. 18 | 19 | Args: 20 | updatable_assets (Union[UpdatableAsset, List[UpdatableAsset]]): Asset(s) to update. 21 | Can be a single UpdatableAsset or a list of UpdatableAssets. 22 | attribute_name (UpdatableAttribute): Name of the attribute to update. 23 | Only userDescription and certificateStatus are allowed. 24 | attribute_values (List[Union[str, CertificateStatus]]): List of values to set for the attribute. 25 | For certificateStatus, only VERIFIED, DRAFT, or DEPRECATED are allowed. 26 | 27 | Returns: 28 | Dict[str, Any]: Dictionary containing: 29 | - updated_count: Number of assets successfully updated 30 | - errors: List of any errors encountered 31 | """ 32 | try: 33 | # Convert single GUID to list for consistent handling 34 | if not isinstance(updatable_assets, list): 35 | updatable_assets = [updatable_assets] 36 | 37 | logger.info( 38 | f"Updating {len(updatable_assets)} assets with attribute '{attribute_name}'" 39 | ) 40 | 41 | # Validate attribute values 42 | if len(updatable_assets) != len(attribute_values): 43 | error_msg = "Number of asset GUIDs must match number of attribute values" 44 | logger.error(error_msg) 45 | return {"updated_count": 0, "errors": [error_msg]} 46 | 47 | # Initialize result tracking 48 | result = {"updated_count": 0, "errors": []} 49 | 50 | # Validate certificate status values if applicable 51 | if attribute_name == UpdatableAttribute.CERTIFICATE_STATUS: 52 | for value in attribute_values: 53 | if value not in CertificateStatus.__members__.values(): 54 | error_msg = f"Invalid certificate status: {value}" 55 | logger.error(error_msg) 56 | result["errors"].append(error_msg) 57 | 58 | # Get Atlan client 59 | client = get_atlan_client() 60 | 61 | # Create assets with updated values 62 | assets = [] 63 | index = 0 64 | for updatable_asset in updatable_assets: 65 | type_name = updatable_asset.type_name 66 | asset_cls = getattr( 67 | __import__("pyatlan.model.assets", fromlist=[type_name]), type_name 68 | ) 69 | asset = asset_cls.updater( 70 | qualified_name=updatable_asset.qualified_name, 71 | name=updatable_asset.name, 72 | ) 73 | setattr(asset, attribute_name.value, attribute_values[index]) 74 | assets.append(asset) 75 | index += 1 76 | response = client.asset.save(assets) 77 | 78 | # Process response 79 | result["updated_count"] = len(response.guid_assignments) 80 | logger.info(f"Successfully updated {result['updated_count']} assets") 81 | return result 82 | 83 | except Exception as e: 84 | error_msg = f"Error updating assets: {str(e)}" 85 | logger.error(error_msg) 86 | return {"updated_count": 0, "errors": [error_msg]} 87 | -------------------------------------------------------------------------------- /modelcontextprotocol/tools/dsl.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | from typing import Dict, Any, Union 4 | 5 | from client import get_atlan_client 6 | from pyatlan.model.search import DSL, IndexSearchRequest 7 | from utils.search import SearchUtils 8 | 9 | # Configure logging 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def get_assets_by_dsl(dsl_query: Union[str, Dict[str, Any]]) -> Dict[str, Any]: 14 | """ 15 | Execute the search with the given query 16 | Args: 17 | dsl_query (Union[str, Dict[str, Any]]): The DSL query as either a string or dictionary 18 | Returns: 19 | Dict[str, Any]: A dictionary containing the results and aggregations 20 | """ 21 | logger.info("Starting DSL-based asset search") 22 | try: 23 | # Parse string to dict if needed 24 | if isinstance(dsl_query, str): 25 | logger.debug("Converting DSL string to JSON") 26 | try: 27 | dsl_dict = json.loads(dsl_query) 28 | except json.JSONDecodeError as e: 29 | logger.error(f"Invalid JSON in DSL query: {e}") 30 | return { 31 | "results": [], 32 | "aggregations": {}, 33 | "error": "Invalid JSON in DSL query", 34 | } 35 | else: 36 | logger.debug("Using provided DSL dictionary") 37 | dsl_dict = dsl_query 38 | 39 | logger.debug("Creating IndexSearchRequest") 40 | index_request = IndexSearchRequest( 41 | dsl=DSL(**dsl_dict), 42 | suppress_logs=True, 43 | show_search_score=True, 44 | exclude_meanings=False, 45 | exclude_atlan_tags=False, 46 | ) 47 | 48 | logger.info("Executing DSL search request") 49 | client = get_atlan_client() 50 | search_response = client.asset.search(index_request) 51 | processed_results = SearchUtils.process_results(search_response) 52 | return processed_results 53 | except Exception as e: 54 | logger.error(f"Error in DSL search: {str(e)}") 55 | return {"results": [], "aggregations": {}, "error": str(e)} 56 | -------------------------------------------------------------------------------- /modelcontextprotocol/tools/lineage.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Dict, Any 3 | 4 | from client import get_atlan_client 5 | from pyatlan.model.enums import LineageDirection 6 | from pyatlan.model.lineage import FluentLineage 7 | 8 | # Configure logging 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | def traverse_lineage( 13 | guid: str, 14 | direction: LineageDirection, 15 | depth: int = 1000000, 16 | size: int = 10, 17 | immediate_neighbors: bool = False, 18 | ) -> Dict[str, Any]: 19 | """ 20 | Traverse asset lineage in specified direction. 21 | 22 | Args: 23 | guid (str): GUID of the starting asset 24 | direction (LineageDirection): Direction to traverse (UPSTREAM or DOWNSTREAM) 25 | depth (int, optional): Maximum depth to traverse. Defaults to 1000000. 26 | size (int, optional): Maximum number of results to return. Defaults to 10. 27 | immediate_neighbors (bool, optional): Only return immediate neighbors. Defaults to True. 28 | 29 | Returns: 30 | Dict[str, Any]: Dictionary containing: 31 | - assets: List of assets in the lineage 32 | - references: List of dictionaries containing: 33 | - source_guid: GUID of the source asset 34 | - target_guid: GUID of the target asset 35 | - direction: Direction of the reference (upstream/downstream) 36 | 37 | Raises: 38 | Exception: If there's an error executing the lineage request 39 | """ 40 | logger.info(f"Starting lineage traversal from {guid} in direction {direction}") 41 | 42 | try: 43 | # Initialize base request 44 | request = ( 45 | FluentLineage(starting_guid=guid) 46 | .direction(direction) 47 | .depth(depth) 48 | .size(size) 49 | .immediate_neighbors(immediate_neighbors) 50 | .request 51 | ) 52 | 53 | # Execute request 54 | logger.debug("Executing lineage request") 55 | client = get_atlan_client() 56 | response = client.asset.get_lineage_list(request) # noqa: F821 57 | 58 | # Process results 59 | result = {"assets": []} 60 | 61 | # Handle None response 62 | if response is None: 63 | logger.info("No lineage results found") 64 | return result 65 | 66 | assets = [] 67 | for item in response: 68 | if item is None: 69 | continue 70 | assets.append(item) 71 | result["assets"] = assets 72 | return result 73 | except Exception as e: 74 | logger.error(f"Error traversing lineage: {str(e)}") 75 | return {"assets": [], "error": str(e)} 76 | -------------------------------------------------------------------------------- /modelcontextprotocol/tools/models.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class CertificateStatus(str, Enum): 8 | """Enum for allowed certificate status values.""" 9 | 10 | VERIFIED = "VERIFIED" 11 | DRAFT = "DRAFT" 12 | DEPRECATED = "DEPRECATED" 13 | 14 | 15 | class UpdatableAttribute(str, Enum): 16 | """Enum for attributes that can be updated.""" 17 | 18 | USER_DESCRIPTION = "user_description" 19 | CERTIFICATE_STATUS = "certificate_status" 20 | 21 | 22 | class UpdatableAsset(BaseModel): 23 | """Class representing an asset that can be updated.""" 24 | 25 | guid: str 26 | name: str 27 | qualified_name: str 28 | type_name: str 29 | user_description: Optional[str] = None 30 | certificate_status: Optional[CertificateStatus] = None 31 | -------------------------------------------------------------------------------- /modelcontextprotocol/tools/search.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Type, List, Optional, Union, Dict, Any 3 | 4 | from client import get_atlan_client 5 | from pyatlan.model.assets import Asset, AtlasGlossaryTerm 6 | from pyatlan.model.fluent_search import CompoundQuery, FluentSearch 7 | from pyatlan.model.fields.atlan_fields import AtlanField 8 | from utils.search import SearchUtils 9 | from utils.constants import DEFAULT_SEARCH_ATTRIBUTES 10 | 11 | # Configure logging 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | def search_assets( 16 | conditions: Optional[Union[Dict[str, Any], str]] = None, 17 | negative_conditions: Optional[Dict[str, Any]] = None, 18 | some_conditions: Optional[Dict[str, Any]] = None, 19 | min_somes: int = 1, 20 | include_attributes: Optional[List[Union[str, AtlanField]]] = None, 21 | asset_type: Optional[Union[Type[Asset], str]] = None, 22 | include_archived: bool = False, 23 | limit: int = 10, 24 | offset: int = 0, 25 | sort_by: Optional[str] = None, 26 | sort_order: str = "ASC", 27 | connection_qualified_name: Optional[str] = None, 28 | tags: Optional[List[str]] = None, 29 | directly_tagged: bool = True, 30 | domain_guids: Optional[List[str]] = None, 31 | date_range: Optional[Dict[str, Dict[str, Any]]] = None, 32 | guids: Optional[List[str]] = None, 33 | ) -> Dict[str, Any]: 34 | """ 35 | Advanced asset search using FluentSearch with flexible conditions. 36 | 37 | By default, only essential attributes used in result processing are included. 38 | Additional attributes can be specified via include_attributes parameter. 39 | 40 | Args: 41 | conditions (Dict[str, Any], optional): Dictionary of attribute conditions to match. 42 | Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}} 43 | negative_conditions (Dict[str, Any], optional): Dictionary of attribute conditions to exclude. 44 | Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}} 45 | some_conditions (Dict[str, Any], optional): Conditions for where_some() queries that require min_somes of them to match. 46 | Format: {"attribute_name": value} or {"attribute_name": {"operator": operator, "value": value}} 47 | min_somes (int): Minimum number of some_conditions that must match. Defaults to 1. 48 | include_attributes (List[Union[str, AtlanField]], optional): List of additional attributes to include in results. 49 | Can be string attribute names or AtlanField objects. These will be added to the default set. 50 | asset_type (Union[Type[Asset], str], optional): Type of asset to search for. 51 | Either a class (e.g., Table, Column) or a string type name (e.g., "Table", "Column") 52 | include_archived (bool): Whether to include archived assets. Defaults to False. 53 | limit (int, optional): Maximum number of results to return. Defaults to 10. 54 | offset (int, optional): Offset for pagination. Defaults to 0. 55 | sort_by (str, optional): Attribute to sort by. Defaults to None. 56 | sort_order (str, optional): Sort order, "ASC" or "DESC". Defaults to "ASC". 57 | connection_qualified_name (str, optional): Connection qualified name to filter by. 58 | tags (List[str], optional): List of tags to filter by. 59 | directly_tagged (bool): Whether to filter for directly tagged assets only. Defaults to True. 60 | domain_guids (List[str], optional): List of domain GUIDs to filter by. 61 | date_range (Dict[str, Dict[str, Any]], optional): Date range filters. 62 | Format: {"attribute_name": {"gte": start_timestamp, "lte": end_timestamp}} 63 | guids (List[str], optional): List of GUIDs to filter by. 64 | 65 | 66 | Returns: 67 | Dict[str, Any]: Dictionary containing: 68 | - results: List of assets matching the search criteria 69 | - aggregations: Search aggregations if available 70 | - error: None if no error occurred, otherwise the error message 71 | """ 72 | logger.info( 73 | f"Starting asset search with parameters: asset_type={asset_type}, " 74 | f"limit={limit}, include_archived={include_archived}" 75 | ) 76 | logger.debug( 77 | f"Full search parameters: conditions={conditions}, " 78 | f"negative_conditions={negative_conditions}, some_conditions={some_conditions}, " 79 | f"include_attributes={include_attributes}, " 80 | f"connection_qualified_name={connection_qualified_name}, " 81 | f"tags={tags}, domain_guids={domain_guids}" 82 | ) 83 | 84 | try: 85 | # Initialize FluentSearch 86 | logger.debug("Initializing FluentSearch object") 87 | search = FluentSearch() 88 | 89 | # Apply asset type filter if provided 90 | if asset_type: 91 | if isinstance(asset_type, str): 92 | # Handle string type name 93 | logger.debug(f"Filtering by asset type name: {asset_type}") 94 | search = search.where(Asset.TYPE_NAME.eq(asset_type)) 95 | else: 96 | # Handle class type 97 | logger.debug(f"Filtering by asset class: {asset_type.__name__}") 98 | search = search.where(CompoundQuery.asset_type(asset_type)) 99 | 100 | # Filter for active assets unless archived are explicitly included 101 | if not include_archived: 102 | logger.debug("Filtering for active assets only") 103 | search = search.where(CompoundQuery.active_assets()) 104 | 105 | # Apply connection qualified name filter if provided 106 | if connection_qualified_name: 107 | logger.debug( 108 | f"Filtering by connection qualified name: {connection_qualified_name}" 109 | ) 110 | search = search.where( 111 | Asset.QUALIFIED_NAME.startswith(connection_qualified_name) 112 | ) 113 | 114 | # Apply tags filter if provided 115 | if tags and len(tags) > 0: 116 | logger.debug( 117 | f"Filtering by tags: {tags}, directly_tagged={directly_tagged}" 118 | ) 119 | search = search.where( 120 | CompoundQuery.tagged(with_one_of=tags, directly=directly_tagged) 121 | ) 122 | 123 | # Apply domain GUIDs filter if provided 124 | if domain_guids and len(domain_guids) > 0: 125 | logger.debug(f"Filtering by domain GUIDs: {domain_guids}") 126 | for guid in domain_guids: 127 | search = search.where(Asset.DOMAIN_GUIDS.eq(guid)) 128 | 129 | # Apply positive conditions 130 | if conditions: 131 | if not isinstance(conditions, dict): 132 | error_msg = f"Conditions parameter must be a dictionary, got {type(conditions).__name__}" 133 | logger.error(error_msg) 134 | return [] 135 | 136 | logger.debug(f"Applying positive conditions: {conditions}") 137 | for attr_name, condition in conditions.items(): 138 | attr = SearchUtils._get_asset_attribute(attr_name) 139 | if attr is None: 140 | logger.warning( 141 | f"Unknown attribute: {attr_name}, skipping condition" 142 | ) 143 | continue 144 | 145 | logger.debug(f"Processing condition for attribute: {attr_name}") 146 | 147 | search = SearchUtils._process_condition( 148 | search, attr, condition, attr_name, "where" 149 | ) 150 | 151 | # Apply negative conditions 152 | if negative_conditions: 153 | logger.debug(f"Applying negative conditions: {negative_conditions}") 154 | for attr_name, condition in negative_conditions.items(): 155 | attr = SearchUtils._get_asset_attribute(attr_name) 156 | if attr is None: 157 | logger.warning( 158 | f"Unknown attribute for negative condition: {attr_name}, skipping" 159 | ) 160 | continue 161 | 162 | logger.debug( 163 | f"Processing negative condition for attribute: {attr_name}" 164 | ) 165 | 166 | search = SearchUtils._process_condition( 167 | search, attr, condition, attr_name, "where_not" 168 | ) 169 | 170 | # Apply where_some conditions with min_somes 171 | if some_conditions: 172 | logger.debug( 173 | f"Applying 'some' conditions: {some_conditions} with min_somes={min_somes}" 174 | ) 175 | for attr_name, condition in some_conditions.items(): 176 | attr = SearchUtils._get_asset_attribute(attr_name) 177 | if attr is None: 178 | logger.warning( 179 | f"Unknown attribute for 'some' condition: {attr_name}, skipping" 180 | ) 181 | continue 182 | 183 | logger.debug(f"Processing 'some' condition for attribute: {attr_name}") 184 | 185 | search = SearchUtils._process_condition( 186 | search, attr, condition, attr_name, "where_some" 187 | ) 188 | search = search.min_somes(min_somes) 189 | 190 | # Apply date range filters 191 | if date_range: 192 | logger.debug(f"Applying date range filters: {date_range}") 193 | date_range_count = 0 194 | for attr_name, range_cond in date_range.items(): 195 | attr = SearchUtils._get_asset_attribute(attr_name) 196 | if attr is None: 197 | logger.warning( 198 | f"Unknown attribute for date range: {attr_name}, skipping" 199 | ) 200 | continue 201 | 202 | logger.debug(f"Processing date range for attribute: {attr_name}") 203 | 204 | if "gte" in range_cond: 205 | logger.debug(f"Adding {attr_name} >= {range_cond['gte']}") 206 | search = search.where(attr.gte(range_cond["gte"])) 207 | date_range_count += 1 208 | if "lte" in range_cond: 209 | logger.debug(f"Adding {attr_name} <= {range_cond['lte']}") 210 | search = search.where(attr.lte(range_cond["lte"])) 211 | date_range_count += 1 212 | if "gt" in range_cond: 213 | logger.debug(f"Adding {attr_name} > {range_cond['gt']}") 214 | search = search.where(attr.gt(range_cond["gt"])) 215 | date_range_count += 1 216 | if "lt" in range_cond: 217 | logger.debug(f"Adding {attr_name} < {range_cond['lt']}") 218 | search = search.where(attr.lt(range_cond["lt"])) 219 | date_range_count += 1 220 | 221 | logger.debug(f"Applied {date_range_count} date range conditions") 222 | 223 | if guids and len(guids) > 0: 224 | logger.debug(f"Applying GUID filter: {guids}") 225 | search = search.where(Asset.GUID.within(guids)) 226 | 227 | # Prepare attributes to include: default attributes + additional user-specified attributes 228 | all_attributes = DEFAULT_SEARCH_ATTRIBUTES.copy() 229 | 230 | if include_attributes: 231 | logger.debug(f"Adding user-specified attributes: {include_attributes}") 232 | for attr in include_attributes: 233 | if isinstance(attr, str): 234 | if attr not in all_attributes: 235 | all_attributes.append(attr) 236 | else: 237 | # For AtlanField objects, we'll add them directly to the search 238 | # They can't be easily compared for duplicates 239 | pass 240 | 241 | logger.debug(f"Total attributes to include: {all_attributes}") 242 | 243 | # Include all attributes in results 244 | for attr_name in all_attributes: 245 | attr_obj = SearchUtils._get_asset_attribute(attr_name) 246 | if attr_obj is None: 247 | logger.warning( 248 | f"Unknown attribute for inclusion: {attr_name}, skipping" 249 | ) 250 | continue 251 | logger.debug(f"Including attribute: {attr_name}") 252 | search = search.include_on_results(attr_obj) 253 | 254 | # Include additional AtlanField objects specified by user 255 | if include_attributes: 256 | for attr in include_attributes: 257 | if not isinstance(attr, str): 258 | # Assume it's already an AtlanField object 259 | logger.debug(f"Including attribute object: {attr}") 260 | search = search.include_on_results(attr) 261 | try: 262 | search = search.include_on_results(Asset.ASSIGNED_TERMS) 263 | search = search.include_on_relations(AtlasGlossaryTerm.NAME) 264 | except Exception as e: 265 | logger.warning(f"Error including assigned terms: {e}") 266 | 267 | # Set pagination 268 | logger.debug(f"Setting pagination: limit={limit}, offset={offset}") 269 | search = search.page_size(limit) 270 | if offset > 0: 271 | search = search.from_offset(offset) 272 | 273 | # Set sorting 274 | if sort_by: 275 | sort_attr = SearchUtils._get_asset_attribute(sort_by) 276 | if sort_attr is not None: 277 | if sort_order.upper() == "DESC": 278 | logger.debug(f"Setting sort order: {sort_by} DESC") 279 | search = search.sort_by_desc(sort_attr) 280 | else: 281 | logger.debug(f"Setting sort order: {sort_by} ASC") 282 | search = search.sort_by_asc(sort_attr) 283 | else: 284 | logger.warning( 285 | f"Unknown attribute for sorting: {sort_by}, skipping sort" 286 | ) 287 | 288 | # Execute search 289 | logger.debug("Converting FluentSearch to request object") 290 | request = search.to_request() 291 | 292 | logger.info("Executing search request") 293 | client = get_atlan_client() 294 | search_response = client.asset.search(request) 295 | processed_results = SearchUtils.process_results(search_response) 296 | logger.info( 297 | f"Search completed, returned {len(processed_results['results'])} results" 298 | ) 299 | return processed_results 300 | 301 | except Exception as e: 302 | logger.error(f"Error searching assets: {str(e)}") 303 | return [{"results": [], "aggregations": {}, "error": str(e)}] 304 | -------------------------------------------------------------------------------- /modelcontextprotocol/utils/constants.py: -------------------------------------------------------------------------------- 1 | DEFAULT_SEARCH_ATTRIBUTES = [ 2 | "name", 3 | "display_name", 4 | "description", 5 | "qualified_name", 6 | "user_description", 7 | "certificate_status", 8 | "owner_users", 9 | "connector_name", 10 | "has_lineage", 11 | "source_created_at", 12 | "source_updated_at", 13 | "readme", 14 | "owner_groups", 15 | "asset_tags", 16 | ] 17 | -------------------------------------------------------------------------------- /modelcontextprotocol/utils/search.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | import logging 3 | from pyatlan.model.assets import Asset 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | class SearchUtils: 9 | @staticmethod 10 | def process_results(results: Any) -> Dict[str, Any]: 11 | """ 12 | Process the results from the search index using Pydantic serialization. 13 | 14 | This method uses Pydantic's .dict(by_alias=True, exclude_unset=True) to: 15 | - Convert field names to their API-friendly camelCase format (by_alias=True) 16 | - Exclude any fields that weren't explicitly set (exclude_unset=True) 17 | 18 | Args: 19 | results: The search results from Atlan 20 | 21 | Returns: 22 | Dict[str, Any]: Dictionary containing: 23 | - results: List of processed results 24 | - aggregations: Search aggregations if available 25 | - error: None if no error occurred, otherwise the error message 26 | """ 27 | current_page_results = ( 28 | results.current_page() 29 | if hasattr(results, "current_page") and callable(results.current_page) 30 | else [] 31 | ) 32 | aggregations = results.aggregations 33 | 34 | logger.info(f"Processing {len(current_page_results)} search results") 35 | results_list = [ 36 | result.dict(by_alias=True, exclude_unset=True) 37 | for result in current_page_results 38 | if result is not None 39 | ] 40 | 41 | return {"results": results_list, "aggregations": aggregations, "error": None} 42 | 43 | @staticmethod 44 | def _get_asset_attribute(attr_name: str): 45 | """ 46 | Get Asset attribute by name. 47 | """ 48 | return getattr(Asset, attr_name.upper(), None) 49 | 50 | @staticmethod 51 | def _apply_operator_condition( 52 | attr, operator: str, value: Any, case_insensitive: bool = False 53 | ): 54 | """ 55 | Apply an operator condition to an attribute. 56 | 57 | Args: 58 | attr: The Asset attribute object 59 | operator (str): The operator to apply 60 | value: The value for the condition 61 | case_insensitive (bool): Whether to apply case insensitive matching 62 | 63 | Returns: 64 | The condition object to be used with where/where_not/where_some 65 | 66 | Raises: 67 | ValueError: If the operator is unknown or value format is invalid 68 | """ 69 | logger.debug( 70 | f"Applying operator '{operator}' with value '{value}' (case_insensitive={case_insensitive})" 71 | ) 72 | 73 | if operator == "startswith": 74 | return attr.startswith(value, case_insensitive=case_insensitive) 75 | elif operator == "match": 76 | return attr.match(value) 77 | elif operator == "eq": 78 | return attr.eq(value, case_insensitive=case_insensitive) 79 | elif operator == "neq": 80 | return attr.neq(value, case_insensitive=case_insensitive) 81 | elif operator == "gte": 82 | return attr.gte(value) 83 | elif operator == "lte": 84 | return attr.lte(value) 85 | elif operator == "gt": 86 | return attr.gt(value) 87 | elif operator == "lt": 88 | return attr.lt(value) 89 | elif operator == "has_any_value": 90 | return attr.has_any_value() 91 | elif operator == "contains": 92 | return attr.contains(value, case_insensitive=case_insensitive) 93 | elif operator == "between": 94 | # Expecting value to be a list/tuple with [start, end] 95 | if isinstance(value, (list, tuple)) and len(value) == 2: 96 | return attr.between(value[0], value[1]) 97 | else: 98 | raise ValueError( 99 | f"Invalid value format for 'between' operator: {value}, expected [start, end]" 100 | ) 101 | else: 102 | # Try to get the operator method from the attribute 103 | op_method = getattr(attr, operator, None) 104 | if op_method is None: 105 | raise ValueError(f"Unknown operator: {operator}") 106 | 107 | # Try to pass case_insensitive if the method supports it 108 | try: 109 | return op_method(value, case_insensitive=case_insensitive) 110 | except TypeError: 111 | # Fallback if case_insensitive is not supported 112 | return op_method(value) 113 | 114 | @staticmethod 115 | def _process_condition( 116 | search, attr, condition, attr_name: str, search_method_name: str 117 | ): 118 | """ 119 | Process a single condition and apply it to the search using the specified method. 120 | 121 | Args: 122 | search: The FluentSearch object 123 | attr: The Asset attribute object 124 | condition: The condition value (dict, list, or simple value) 125 | attr_name (str): The attribute name for logging 126 | search_method_name (str): The search method to use ('where', 'where_not', 'where_some') 127 | 128 | Returns: 129 | FluentSearch: The updated search object 130 | """ 131 | search_method = getattr(search, search_method_name) 132 | 133 | if isinstance(condition, dict): 134 | operator = condition.get("operator", "eq") 135 | value = condition.get("value") 136 | case_insensitive = condition.get("case_insensitive", False) 137 | 138 | try: 139 | condition_obj = SearchUtils._apply_operator_condition( 140 | attr, operator, value, case_insensitive 141 | ) 142 | search = search_method(condition_obj) 143 | return search 144 | except ValueError as e: 145 | logger.warning(f"Skipping condition for {attr_name}: {e}") 146 | return search 147 | elif isinstance(condition, list): 148 | if search_method_name == "where_some": 149 | # Handle multiple values for where_some 150 | logger.debug( 151 | f"Adding multiple '{search_method_name}' values for {attr_name}: {condition}" 152 | ) 153 | for value in condition: 154 | search = search_method(attr.eq(value)) 155 | return search 156 | else: 157 | # Handle list of values with OR logic using .within() 158 | logger.debug(f"Applying multiple values for {attr_name}: {condition}") 159 | search = search_method(attr.within(condition)) 160 | return search 161 | elif condition == "has_any_value" and search_method_name == "where_not": 162 | # Special case for has_any_value in negative conditions 163 | logger.debug(f"Excluding assets where {attr_name} has any value") 164 | search = search_method(attr.has_any_value()) 165 | return search 166 | else: 167 | # Default to equality operator 168 | logger.debug( 169 | f"Applying {search_method_name} equality condition {attr_name}={condition}" 170 | ) 171 | search = search_method(attr.eq(condition)) 172 | return search 173 | -------------------------------------------------------------------------------- /modelcontextprotocol/uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.11" 4 | 5 | [[package]] 6 | name = "annotated-types" 7 | version = "0.7.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 12 | ] 13 | 14 | [[package]] 15 | name = "anyio" 16 | version = "4.9.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "idna" }, 20 | { name = "sniffio" }, 21 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 22 | ] 23 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } 24 | wheels = [ 25 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, 26 | ] 27 | 28 | [[package]] 29 | name = "atlan-mcp-server" 30 | source = { editable = "." } 31 | dependencies = [ 32 | { name = "fastmcp" }, 33 | { name = "pyatlan" }, 34 | ] 35 | 36 | [package.metadata] 37 | requires-dist = [ 38 | { name = "fastmcp", specifier = ">=2.3.4" }, 39 | { name = "pyatlan", specifier = ">=6.0.1" }, 40 | ] 41 | 42 | [[package]] 43 | name = "certifi" 44 | version = "2025.1.31" 45 | source = { registry = "https://pypi.org/simple" } 46 | sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } 47 | wheels = [ 48 | { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, 49 | ] 50 | 51 | [[package]] 52 | name = "charset-normalizer" 53 | version = "3.4.1" 54 | source = { registry = "https://pypi.org/simple" } 55 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } 56 | wheels = [ 57 | { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, 58 | { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, 59 | { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, 60 | { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, 61 | { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, 62 | { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, 63 | { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, 64 | { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, 65 | { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, 66 | { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, 67 | { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, 68 | { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, 69 | { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, 70 | { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, 71 | { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, 72 | { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, 73 | { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, 74 | { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, 75 | { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, 76 | { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, 77 | { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, 78 | { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, 79 | { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, 80 | { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, 81 | { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, 82 | { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, 83 | { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, 84 | { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, 85 | { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, 86 | { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, 87 | { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, 88 | { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, 89 | { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, 90 | { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, 91 | { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, 92 | { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, 93 | { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, 94 | { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, 95 | { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, 96 | { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, 97 | ] 98 | 99 | [[package]] 100 | name = "click" 101 | version = "8.1.8" 102 | source = { registry = "https://pypi.org/simple" } 103 | dependencies = [ 104 | { name = "colorama", marker = "sys_platform == 'win32'" }, 105 | ] 106 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 107 | wheels = [ 108 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 109 | ] 110 | 111 | [[package]] 112 | name = "colorama" 113 | version = "0.4.6" 114 | source = { registry = "https://pypi.org/simple" } 115 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 116 | wheels = [ 117 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 118 | ] 119 | 120 | [[package]] 121 | name = "exceptiongroup" 122 | version = "1.3.0" 123 | source = { registry = "https://pypi.org/simple" } 124 | dependencies = [ 125 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 126 | ] 127 | sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } 128 | wheels = [ 129 | { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, 130 | ] 131 | 132 | [[package]] 133 | name = "fastmcp" 134 | version = "2.3.4" 135 | source = { registry = "https://pypi.org/simple" } 136 | dependencies = [ 137 | { name = "exceptiongroup" }, 138 | { name = "httpx" }, 139 | { name = "mcp" }, 140 | { name = "openapi-pydantic" }, 141 | { name = "python-dotenv" }, 142 | { name = "rich" }, 143 | { name = "typer" }, 144 | { name = "websockets" }, 145 | ] 146 | sdist = { url = "https://files.pythonhosted.org/packages/75/d9/cc3eb61c59fec834a9492ea21df134381b4be76c35faa18cd2b0249278b8/fastmcp-2.3.4.tar.gz", hash = "sha256:f3fe004b8735b365a65ec2547eeb47db8352d5613697254854bc7c9c3c360eea", size = 998315 } 147 | wheels = [ 148 | { url = "https://files.pythonhosted.org/packages/a0/e6/310d1fe6708b7338e1f48915a13d8bf00fd0599acdc7bf98da4fd20fcb66/fastmcp-2.3.4-py3-none-any.whl", hash = "sha256:12a45f72dd95aeaa1a6a56281fff96ca46929def3ccd9f9eb125cb97b722fbab", size = 96393 }, 149 | ] 150 | 151 | [[package]] 152 | name = "h11" 153 | version = "0.14.0" 154 | source = { registry = "https://pypi.org/simple" } 155 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 156 | wheels = [ 157 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 158 | ] 159 | 160 | [[package]] 161 | name = "httpcore" 162 | version = "1.0.7" 163 | source = { registry = "https://pypi.org/simple" } 164 | dependencies = [ 165 | { name = "certifi" }, 166 | { name = "h11" }, 167 | ] 168 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } 169 | wheels = [ 170 | { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, 171 | ] 172 | 173 | [[package]] 174 | name = "httpx" 175 | version = "0.28.1" 176 | source = { registry = "https://pypi.org/simple" } 177 | dependencies = [ 178 | { name = "anyio" }, 179 | { name = "certifi" }, 180 | { name = "httpcore" }, 181 | { name = "idna" }, 182 | ] 183 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 184 | wheels = [ 185 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 186 | ] 187 | 188 | [[package]] 189 | name = "httpx-sse" 190 | version = "0.4.0" 191 | source = { registry = "https://pypi.org/simple" } 192 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 193 | wheels = [ 194 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 195 | ] 196 | 197 | [[package]] 198 | name = "idna" 199 | version = "3.10" 200 | source = { registry = "https://pypi.org/simple" } 201 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 202 | wheels = [ 203 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 204 | ] 205 | 206 | [[package]] 207 | name = "jinja2" 208 | version = "3.1.6" 209 | source = { registry = "https://pypi.org/simple" } 210 | dependencies = [ 211 | { name = "markupsafe" }, 212 | ] 213 | sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } 214 | wheels = [ 215 | { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, 216 | ] 217 | 218 | [[package]] 219 | name = "lazy-loader" 220 | version = "0.4" 221 | source = { registry = "https://pypi.org/simple" } 222 | dependencies = [ 223 | { name = "packaging" }, 224 | ] 225 | sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431 } 226 | wheels = [ 227 | { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097 }, 228 | ] 229 | 230 | [[package]] 231 | name = "markdown-it-py" 232 | version = "3.0.0" 233 | source = { registry = "https://pypi.org/simple" } 234 | dependencies = [ 235 | { name = "mdurl" }, 236 | ] 237 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 238 | wheels = [ 239 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 240 | ] 241 | 242 | [[package]] 243 | name = "markupsafe" 244 | version = "3.0.2" 245 | source = { registry = "https://pypi.org/simple" } 246 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } 247 | wheels = [ 248 | { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, 249 | { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, 250 | { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, 251 | { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, 252 | { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, 253 | { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, 254 | { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, 255 | { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, 256 | { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, 257 | { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, 258 | { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, 259 | { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, 260 | { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, 261 | { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, 262 | { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, 263 | { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, 264 | { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, 265 | { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, 266 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, 267 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, 268 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, 269 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, 270 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, 271 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, 272 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, 273 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, 274 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, 275 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, 276 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, 277 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, 278 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, 279 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, 280 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, 281 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, 282 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, 283 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, 284 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, 285 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, 286 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, 287 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, 288 | ] 289 | 290 | [[package]] 291 | name = "mcp" 292 | version = "1.8.1" 293 | source = { registry = "https://pypi.org/simple" } 294 | dependencies = [ 295 | { name = "anyio" }, 296 | { name = "httpx" }, 297 | { name = "httpx-sse" }, 298 | { name = "pydantic" }, 299 | { name = "pydantic-settings" }, 300 | { name = "python-multipart" }, 301 | { name = "sse-starlette" }, 302 | { name = "starlette" }, 303 | { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, 304 | ] 305 | sdist = { url = "https://files.pythonhosted.org/packages/7c/13/16b712e8a3be6a736b411df2fc6b4e75eb1d3e99b1cd57a3a1decf17f612/mcp-1.8.1.tar.gz", hash = "sha256:ec0646271d93749f784d2316fb5fe6102fb0d1be788ec70a9e2517e8f2722c0e", size = 265605 } 306 | wheels = [ 307 | { url = "https://files.pythonhosted.org/packages/1c/5d/91cf0d40e40ae9ecf8d4004e0f9611eea86085aa0b5505493e0ff53972da/mcp-1.8.1-py3-none-any.whl", hash = "sha256:948e03783859fa35abe05b9b6c0a1d5519be452fc079dc8d7f682549591c1770", size = 119761 }, 308 | ] 309 | 310 | [[package]] 311 | name = "mdurl" 312 | version = "0.1.2" 313 | source = { registry = "https://pypi.org/simple" } 314 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 315 | wheels = [ 316 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 317 | ] 318 | 319 | [[package]] 320 | name = "nanoid" 321 | version = "2.0.0" 322 | source = { registry = "https://pypi.org/simple" } 323 | sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/0250bf5935d88e214df469d35eccc0f6ff7e9db046fc8a9aeb4b2a192775/nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68", size = 3290 } 324 | wheels = [ 325 | { url = "https://files.pythonhosted.org/packages/2e/0d/8630f13998638dc01e187fadd2e5c6d42d127d08aeb4943d231664d6e539/nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb", size = 5844 }, 326 | ] 327 | 328 | [[package]] 329 | name = "openapi-pydantic" 330 | version = "0.5.1" 331 | source = { registry = "https://pypi.org/simple" } 332 | dependencies = [ 333 | { name = "pydantic" }, 334 | ] 335 | sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892 } 336 | wheels = [ 337 | { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381 }, 338 | ] 339 | 340 | [[package]] 341 | name = "packaging" 342 | version = "24.2" 343 | source = { registry = "https://pypi.org/simple" } 344 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 345 | wheels = [ 346 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 347 | ] 348 | 349 | [[package]] 350 | name = "pyatlan" 351 | version = "6.0.1" 352 | source = { registry = "https://pypi.org/simple" } 353 | dependencies = [ 354 | { name = "jinja2" }, 355 | { name = "lazy-loader" }, 356 | { name = "nanoid" }, 357 | { name = "pydantic" }, 358 | { name = "python-dateutil" }, 359 | { name = "pytz" }, 360 | { name = "pyyaml" }, 361 | { name = "requests" }, 362 | { name = "tenacity" }, 363 | { name = "urllib3" }, 364 | ] 365 | sdist = { url = "https://files.pythonhosted.org/packages/1a/0f/a02b6b4bdee51dc93d95501b48d4a8b6cfd592f9b9873956543b44d80e8f/pyatlan-6.0.1.tar.gz", hash = "sha256:5f0b74db563ffea2ba101f46514054f8a15ea1a9760cc421bdb9a4aeb30ee870", size = 706359 } 366 | wheels = [ 367 | { url = "https://files.pythonhosted.org/packages/ad/33/4ad4795a22aab4114f444e1e9c110e17bd95dafc3242f2cccf21814ba6d5/pyatlan-6.0.1-py3-none-any.whl", hash = "sha256:a96da3c81a1e877ad134b6bdfce97694833ab1b0d043939c2d786f18d54df292", size = 1166474 }, 368 | ] 369 | 370 | [[package]] 371 | name = "pydantic" 372 | version = "2.10.6" 373 | source = { registry = "https://pypi.org/simple" } 374 | dependencies = [ 375 | { name = "annotated-types" }, 376 | { name = "pydantic-core" }, 377 | { name = "typing-extensions" }, 378 | ] 379 | sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } 380 | wheels = [ 381 | { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, 382 | ] 383 | 384 | [[package]] 385 | name = "pydantic-core" 386 | version = "2.27.2" 387 | source = { registry = "https://pypi.org/simple" } 388 | dependencies = [ 389 | { name = "typing-extensions" }, 390 | ] 391 | sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } 392 | wheels = [ 393 | { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, 394 | { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, 395 | { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, 396 | { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, 397 | { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, 398 | { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, 399 | { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, 400 | { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, 401 | { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, 402 | { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, 403 | { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, 404 | { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, 405 | { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, 406 | { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, 407 | { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, 408 | { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, 409 | { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, 410 | { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, 411 | { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, 412 | { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, 413 | { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, 414 | { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, 415 | { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, 416 | { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, 417 | { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, 418 | { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, 419 | { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, 420 | { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, 421 | { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, 422 | { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, 423 | { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, 424 | { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, 425 | { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, 426 | { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, 427 | { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, 428 | { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, 429 | { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, 430 | { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, 431 | { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, 432 | { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, 433 | { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, 434 | { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, 435 | ] 436 | 437 | [[package]] 438 | name = "pydantic-settings" 439 | version = "2.8.1" 440 | source = { registry = "https://pypi.org/simple" } 441 | dependencies = [ 442 | { name = "pydantic" }, 443 | { name = "python-dotenv" }, 444 | ] 445 | sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 } 446 | wheels = [ 447 | { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 }, 448 | ] 449 | 450 | [[package]] 451 | name = "pygments" 452 | version = "2.19.1" 453 | source = { registry = "https://pypi.org/simple" } 454 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 455 | wheels = [ 456 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 457 | ] 458 | 459 | [[package]] 460 | name = "python-dateutil" 461 | version = "2.9.0.post0" 462 | source = { registry = "https://pypi.org/simple" } 463 | dependencies = [ 464 | { name = "six" }, 465 | ] 466 | sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } 467 | wheels = [ 468 | { 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 }, 469 | ] 470 | 471 | [[package]] 472 | name = "python-dotenv" 473 | version = "1.1.0" 474 | source = { registry = "https://pypi.org/simple" } 475 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } 476 | wheels = [ 477 | { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, 478 | ] 479 | 480 | [[package]] 481 | name = "python-multipart" 482 | version = "0.0.20" 483 | source = { registry = "https://pypi.org/simple" } 484 | sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } 485 | wheels = [ 486 | { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, 487 | ] 488 | 489 | [[package]] 490 | name = "pytz" 491 | version = "2025.2" 492 | source = { registry = "https://pypi.org/simple" } 493 | sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } 494 | wheels = [ 495 | { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, 496 | ] 497 | 498 | [[package]] 499 | name = "pyyaml" 500 | version = "6.0.2" 501 | source = { registry = "https://pypi.org/simple" } 502 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 503 | wheels = [ 504 | { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, 505 | { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, 506 | { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, 507 | { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, 508 | { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, 509 | { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, 510 | { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, 511 | { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, 512 | { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, 513 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 514 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 515 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 516 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 517 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 518 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 519 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 520 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 521 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 522 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, 523 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, 524 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, 525 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, 526 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, 527 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, 528 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, 529 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 530 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 531 | ] 532 | 533 | [[package]] 534 | name = "requests" 535 | version = "2.32.3" 536 | source = { registry = "https://pypi.org/simple" } 537 | dependencies = [ 538 | { name = "certifi" }, 539 | { name = "charset-normalizer" }, 540 | { name = "idna" }, 541 | { name = "urllib3" }, 542 | ] 543 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } 544 | wheels = [ 545 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, 546 | ] 547 | 548 | [[package]] 549 | name = "rich" 550 | version = "14.0.0" 551 | source = { registry = "https://pypi.org/simple" } 552 | dependencies = [ 553 | { name = "markdown-it-py" }, 554 | { name = "pygments" }, 555 | ] 556 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } 557 | wheels = [ 558 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, 559 | ] 560 | 561 | [[package]] 562 | name = "shellingham" 563 | version = "1.5.4" 564 | source = { registry = "https://pypi.org/simple" } 565 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 566 | wheels = [ 567 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, 568 | ] 569 | 570 | [[package]] 571 | name = "six" 572 | version = "1.17.0" 573 | source = { registry = "https://pypi.org/simple" } 574 | sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } 575 | wheels = [ 576 | { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, 577 | ] 578 | 579 | [[package]] 580 | name = "sniffio" 581 | version = "1.3.1" 582 | source = { registry = "https://pypi.org/simple" } 583 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 584 | wheels = [ 585 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 586 | ] 587 | 588 | [[package]] 589 | name = "sse-starlette" 590 | version = "2.2.1" 591 | source = { registry = "https://pypi.org/simple" } 592 | dependencies = [ 593 | { name = "anyio" }, 594 | { name = "starlette" }, 595 | ] 596 | sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } 597 | wheels = [ 598 | { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, 599 | ] 600 | 601 | [[package]] 602 | name = "starlette" 603 | version = "0.46.1" 604 | source = { registry = "https://pypi.org/simple" } 605 | dependencies = [ 606 | { name = "anyio" }, 607 | ] 608 | sdist = { url = "https://files.pythonhosted.org/packages/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102 } 609 | wheels = [ 610 | { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995 }, 611 | ] 612 | 613 | [[package]] 614 | name = "tenacity" 615 | version = "9.0.0" 616 | source = { registry = "https://pypi.org/simple" } 617 | sdist = { url = "https://files.pythonhosted.org/packages/cd/94/91fccdb4b8110642462e653d5dcb27e7b674742ad68efd146367da7bdb10/tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b", size = 47421 } 618 | wheels = [ 619 | { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169 }, 620 | ] 621 | 622 | [[package]] 623 | name = "typer" 624 | version = "0.15.2" 625 | source = { registry = "https://pypi.org/simple" } 626 | dependencies = [ 627 | { name = "click" }, 628 | { name = "rich" }, 629 | { name = "shellingham" }, 630 | { name = "typing-extensions" }, 631 | ] 632 | sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } 633 | wheels = [ 634 | { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, 635 | ] 636 | 637 | [[package]] 638 | name = "typing-extensions" 639 | version = "4.13.1" 640 | source = { registry = "https://pypi.org/simple" } 641 | sdist = { url = "https://files.pythonhosted.org/packages/76/ad/cd3e3465232ec2416ae9b983f27b9e94dc8171d56ac99b345319a9475967/typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff", size = 106633 } 642 | wheels = [ 643 | { url = "https://files.pythonhosted.org/packages/df/c5/e7a0b0f5ed69f94c8ab7379c599e6036886bffcde609969a5325f47f1332/typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69", size = 45739 }, 644 | ] 645 | 646 | [[package]] 647 | name = "urllib3" 648 | version = "2.3.0" 649 | source = { registry = "https://pypi.org/simple" } 650 | sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } 651 | wheels = [ 652 | { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, 653 | ] 654 | 655 | [[package]] 656 | name = "uvicorn" 657 | version = "0.34.0" 658 | source = { registry = "https://pypi.org/simple" } 659 | dependencies = [ 660 | { name = "click" }, 661 | { name = "h11" }, 662 | ] 663 | sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } 664 | wheels = [ 665 | { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315 }, 666 | ] 667 | 668 | [[package]] 669 | name = "websockets" 670 | version = "15.0.1" 671 | source = { registry = "https://pypi.org/simple" } 672 | sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } 673 | wheels = [ 674 | { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423 }, 675 | { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082 }, 676 | { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330 }, 677 | { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878 }, 678 | { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883 }, 679 | { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252 }, 680 | { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521 }, 681 | { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958 }, 682 | { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918 }, 683 | { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388 }, 684 | { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828 }, 685 | { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 }, 686 | { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 }, 687 | { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 }, 688 | { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 }, 689 | { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 }, 690 | { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 }, 691 | { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 }, 692 | { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 }, 693 | { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 }, 694 | { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, 695 | { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, 696 | { 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 }, 697 | { 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 }, 698 | { 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 }, 699 | { 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 }, 700 | { 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 }, 701 | { 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 }, 702 | { 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 }, 703 | { 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 }, 704 | { 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 }, 705 | { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, 706 | { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, 707 | { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, 708 | ] 709 | -------------------------------------------------------------------------------- /modelcontextprotocol/version.py: -------------------------------------------------------------------------------- 1 | """Version information.""" 2 | 3 | __version__ = "0.2.1" 4 | --------------------------------------------------------------------------------