diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index b18fd29..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: 'github-actions' - directory: '/' - schedule: - interval: 'weekly' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index dd93c47..f6d5e1a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,15 +2,9 @@ ## Description -## Publishing Your Server - -**Note: We are no longer accepting PRs to add servers to the README.** Instead, please publish your server to the [MCP Server Registry](https://github.com/modelcontextprotocol/registry) to make it discoverable to the MCP ecosystem. - -To publish your server, follow the [quickstart guide](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/quickstart.mdx). You can browse published servers at [https://registry.modelcontextprotocol.io/](https://registry.modelcontextprotocol.io/). - ## Server Details - -- Server: + +- Server: - Changes to: ## Motivation and Context @@ -24,6 +18,7 @@ To publish your server, follow the [quickstart guide](https://github.com/modelco ## Types of changes +- [ ] New MCP Server - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) @@ -32,7 +27,7 @@ To publish your server, follow the [quickstart guide](https://github.com/modelco ## Checklist - [ ] I have read the [MCP Protocol Documentation](https://modelcontextprotocol.io) -- [ ] My changes follows MCP security best practices +- [ ] My server follows MCP security best practices - [ ] I have updated the server's README accordingly - [ ] I have tested this with an LLM client - [ ] My code follows the repository's style guidelines diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index 92c74fc..0000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - actions: read - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - - # Allow Claude to read CI results on PRs - additional_permissions: | - actions: read - - # Trigger when assigned to an issue - assignee_trigger: "claude" - - claude_args: | - --mcp-config .mcp.json - --allowedTools "Bash,mcp__mcp-docs,WebFetch" - --append-system-prompt "If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a
block. When working on MCP-related code or reviewing MCP-related changes, use the mcp-docs MCP server to look up the latest protocol documentation. For schema details, reference https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema which contains versioned schemas in JSON (schema.json) and TypeScript (schema.ts) formats." diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index be17139..a1edc0c 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -2,13 +2,11 @@ name: Python on: push: + branches: + - main pull_request: - -# Superseded runs on the same ref are cancelled (e.g. rapid amend-pushes), -# except on main, where every merge keeps its own CI run -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + release: + types: [published] jobs: detect-packages: @@ -16,7 +14,7 @@ jobs: outputs: packages: ${{ steps.find-packages.outputs.packages }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 - name: Find Python packages id: find-packages @@ -25,64 +23,27 @@ jobs: PACKAGES=$(find . -name pyproject.toml -exec dirname {} \; | sed 's/^\.\///' | jq -R -s -c 'split("\n")[:-1]') echo "packages=$PACKAGES" >> $GITHUB_OUTPUT - test: - needs: [detect-packages] - strategy: - matrix: - package: ${{ fromJson(needs.detect-packages.outputs.packages) }} - name: Test ${{ matrix.package }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Set up Python - uses: actions/setup-python@v7 - with: - python-version-file: "src/${{ matrix.package }}/.python-version" - - - name: Install dependencies - working-directory: src/${{ matrix.package }} - run: uv sync --frozen --all-extras --dev - - - name: Check if tests exist - id: check-tests - working-directory: src/${{ matrix.package }} - run: | - if [ -d "tests" ] || [ -d "test" ] || grep -q "pytest" pyproject.toml; then - echo "has-tests=true" >> $GITHUB_OUTPUT - else - echo "has-tests=false" >> $GITHUB_OUTPUT - fi - - - name: Run tests - if: steps.check-tests.outputs.has-tests == 'true' - working-directory: src/${{ matrix.package }} - run: uv run pytest - build: - needs: [detect-packages, test] + needs: [detect-packages] strategy: matrix: package: ${{ fromJson(needs.detect-packages.outputs.packages) }} name: Build ${{ matrix.package }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v3 - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@v5 with: python-version-file: "src/${{ matrix.package }}/.python-version" - name: Install dependencies working-directory: src/${{ matrix.package }} - run: uv sync --locked --all-extras --dev + run: uv sync --frozen --all-extras --dev - name: Run pyright working-directory: src/${{ matrix.package }} @@ -93,7 +54,31 @@ jobs: run: uv build - name: Upload artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v4 with: name: dist-${{ matrix.package }} path: src/${{ matrix.package }}/dist/ + + publish: + runs-on: ubuntu-latest + needs: [build, detect-packages] + if: github.event_name == 'release' + + strategy: + matrix: + package: ${{ fromJson(needs.detect-packages.outputs.packages) }} + name: Publish ${{ matrix.package }} + + environment: release + permissions: + id-token: write # Required for trusted publishing + + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: dist-${{ matrix.package }} + path: dist/ + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/readme-pr-check.yml b/.github/workflows/readme-pr-check.yml deleted file mode 100644 index ec5a0a2..0000000 --- a/.github/workflows/readme-pr-check.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: README PR Check - -on: - # pull_request_target, not pull_request: fork PRs get a read-only token under - # pull_request, so the label and comment calls fail with 403. This job only - # calls the API and never checks out PR code, so the write token is safe. - pull_request_target: - types: [opened] - paths: - - 'README.md' - issue_comment: - types: [created] - -jobs: - check-readme-only: - if: github.event_name == 'pull_request_target' - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Check files and comment if README-only - uses: actions/github-script@v8 - with: - script: | - const { owner, repo } = context.repo; - const prNumber = context.payload.pull_request.number; - - const { data: files } = await github.rest.pulls.listFiles({ owner, repo, pull_number: prNumber }); - - if (files.length !== 1 || files[0].filename !== 'README.md') { - console.log('PR modifies files other than README, skipping'); - return; - } - - // Check if we've already commented - const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: prNumber }); - if (comments.some(c => c.user.login === 'github-actions[bot]' && c.body.includes('no longer accepting PRs to add new servers'))) { - console.log('Already commented on this PR, skipping'); - return; - } - - await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['readme: pending'] }); - - await github.rest.issues.createComment({ - owner, - repo, - issue_number: prNumber, - body: [ - 'Thanks for your contribution!', - '', - '**We are no longer accepting PRs to add new servers to the README.** The third-party server list has been removed from this README in favor of the [MCP Server Registry](https://registry.modelcontextprotocol.io/).', - '', - '๐Ÿ‘‰ **To add a new MCP server:** Please publish it to the [MCP Server Registry](https://github.com/modelcontextprotocol/registry) instead. You can browse published servers at [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io/).', - '', - '๐Ÿ‘‰ **If this PR updates or removes an existing entry:** We do still accept these changes. Please reply with `/i-promise-this-is-not-a-new-server` to continue.', - '', - 'If this PR is adding a new server, please close it and submit to the registry instead.', - ].join('\n'), - }); - - handle-confirmation: - if: github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '/i-promise-this-is-not-a-new-server') - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Swap labels and minimize comments - uses: actions/github-script@v8 - with: - script: | - const { owner, repo } = context.repo; - const prNumber = context.payload.issue.number; - - // Check if pending label exists - const { data: labels } = await github.rest.issues.listLabelsOnIssue({ owner, repo, issue_number: prNumber }); - if (!labels.some(l => l.name === 'readme: pending')) { - console.log('No pending label found, skipping'); - return; - } - - // Swap labels - try { - await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'readme: pending' }); - } catch (e) {} - await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['readme: ready for review'] }); - - // Find the bot's original comment - const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: prNumber }); - const botComment = comments.find(c => - c.user.login === 'github-actions[bot]' && - c.body.includes('no longer accepting PRs to add new servers') - ); - - // Minimize both comments via GraphQL - const minimizeComment = async (nodeId) => { - await github.graphql(` - mutation($id: ID!) { - minimizeComment(input: {subjectId: $id, classifier: RESOLVED}) { - minimizedComment { isMinimized } - } - } - `, { id: nodeId }); - }; - - if (botComment) { - await minimizeComment(botComment.node_id); - } - - // Only minimize user's comment if it's just the command - const userComment = context.payload.comment.body.trim(); - if (userComment === '/i-promise-this-is-not-a-new-server') { - await minimizeComment(context.payload.comment.node_id); - } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index fd05add..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,243 +0,0 @@ -name: Release - -on: - workflow_dispatch: - -jobs: - create-metadata: - runs-on: ubuntu-latest - if: github.repository_owner == 'modelcontextprotocol' - outputs: - hash: ${{ steps.last-release.outputs.hash }} - version: ${{ steps.create-version.outputs.version}} - npm_packages: ${{ steps.create-npm-packages.outputs.npm_packages}} - pypi_packages: ${{ steps.create-pypi-packages.outputs.pypi_packages}} - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Get last release hash - id: last-release - run: | - HASH=$(git rev-list --tags --max-count=1 || echo "HEAD~1") - echo "hash=${HASH}" >> $GITHUB_OUTPUT - echo "Using last release hash: ${HASH}" - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Create version name - id: create-version - run: | - VERSION=$(uv run --script scripts/release.py generate-version) - echo "version $VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Create notes - run: | - HASH="${{ steps.last-release.outputs.hash }}" - uv run --script scripts/release.py generate-notes --directory src/ $HASH > RELEASE_NOTES.md - cat RELEASE_NOTES.md - - - name: Release notes - uses: actions/upload-artifact@v7 - with: - name: release-notes - path: RELEASE_NOTES.md - - - name: Create python matrix - id: create-pypi-packages - run: | - HASH="${{ steps.last-release.outputs.hash }}" - PYPI=$(uv run --script scripts/release.py generate-matrix --pypi --directory src $HASH) - echo "pypi_packages $PYPI" - echo "pypi_packages=$PYPI" >> $GITHUB_OUTPUT - - - name: Create npm matrix - id: create-npm-packages - run: | - HASH="${{ steps.last-release.outputs.hash }}" - NPM=$(uv run --script scripts/release.py generate-matrix --npm --directory src $HASH) - echo "npm_packages $NPM" - echo "npm_packages=$NPM" >> $GITHUB_OUTPUT - - update-packages: - needs: [create-metadata] - if: ${{ needs.create-metadata.outputs.npm_packages != '[]' || needs.create-metadata.outputs.pypi_packages != '[]' }} - runs-on: ubuntu-latest - environment: release - permissions: - contents: write - outputs: - changes_made: ${{ steps.commit.outputs.changes_made }} - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Update packages - run: | - HASH="${{ needs.create-metadata.outputs.hash }}" - uv run --script scripts/release.py update-packages --directory src/ $HASH - - - name: Configure git - run: | - git config --global user.name "GitHub Actions" - git config --global user.email "actions@github.com" - - - name: Commit changes - id: commit - run: | - VERSION="${{ needs.create-metadata.outputs.version }}" - git add -u - if git diff-index --quiet HEAD; then - echo "changes_made=false" >> $GITHUB_OUTPUT - else - git commit -m 'Automatic update of packages' - git tag -a "$VERSION" -m "Release $VERSION" - git push origin "$VERSION" - echo "changes_made=true" >> $GITHUB_OUTPUT - fi - - publish-pypi: - needs: [update-packages, create-metadata] - if: ${{ needs.create-metadata.outputs.pypi_packages != '[]' && needs.create-metadata.outputs.pypi_packages != '' }} - strategy: - fail-fast: false - matrix: - package: ${{ fromJson(needs.create-metadata.outputs.pypi_packages) }} - name: Build ${{ matrix.package }} - environment: release - permissions: - id-token: write # Required for trusted publishing - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - ref: ${{ needs.create-metadata.outputs.version }} - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Set up Python - uses: actions/setup-python@v7 - with: - python-version-file: "src/${{ matrix.package }}/.python-version" - - - name: Install dependencies - working-directory: src/${{ matrix.package }} - run: uv sync --frozen --all-extras --dev - - - name: Run pyright - working-directory: src/${{ matrix.package }} - run: uv run --frozen pyright - - - name: Run tests - working-directory: src/${{ matrix.package }} - run: | - if [ -d "tests" ] || [ -d "test" ]; then - uv run --frozen pytest - else - echo "No tests directory, skipping" - fi - - - name: Build package - working-directory: src/${{ matrix.package }} - run: uv build - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: src/${{ matrix.package }}/dist - skip-existing: true # re-runs tolerate already-uploaded files - - publish-npm: - needs: [update-packages, create-metadata] - if: ${{ needs.create-metadata.outputs.npm_packages != '[]' && needs.create-metadata.outputs.npm_packages != '' }} - strategy: - fail-fast: false - matrix: - package: ${{ fromJson(needs.create-metadata.outputs.npm_packages) }} - name: Build ${{ matrix.package }} - environment: release - permissions: - contents: read - id-token: write # Required for npm trusted publishing (OIDC) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - ref: ${{ needs.create-metadata.outputs.version }} - - - uses: actions/setup-node@v7 - with: - node-version: 22 - cache: npm - registry-url: 'https://registry.npmjs.org' - - # OIDC trusted publishing requires npm >=11.5.1; Node 22's bundled npm is 10.x. - - name: Ensure npm CLI supports OIDC trusted publishing - run: npm install -g npm@^11.5.1 - - - name: Install dependencies - working-directory: src/${{ matrix.package }} - run: npm ci - - - name: Check if version exists on npm - working-directory: src/${{ matrix.package }} - run: | - VERSION=$(jq -r .version package.json) - if npm view --json | jq -e --arg version "$VERSION" '[.[]][0].versions | contains([$version])'; then - echo "Version $VERSION already exists on npm" - exit 1 - fi - echo "Version $VERSION is new, proceeding with publish" - - - name: Run tests - working-directory: src/${{ matrix.package }} - run: npm test --if-present - - - name: Build package - working-directory: src/${{ matrix.package }} - run: npm run build - - # Authenticates via OIDC trusted publishing (no token) โ€” each package's - # npm trusted publisher is bound to this workflow + the release environment - - name: Publish package - working-directory: src/${{ matrix.package }} - run: | - npm publish --access public - env: - NPM_CONFIG_PROVENANCE: "true" - - create-release: - needs: [update-packages, create-metadata, publish-pypi, publish-npm] - if: | - always() && - needs.update-packages.outputs.changes_made == 'true' && - (needs.publish-pypi.result == 'success' || needs.publish-npm.result == 'success') - runs-on: ubuntu-latest - environment: release - permissions: - contents: write - steps: - - uses: actions/checkout@v6 - - - name: Download release notes - uses: actions/download-artifact@v8 - with: - name: release-notes - - - name: Create release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN}} - run: | - VERSION="${{ needs.create-metadata.outputs.version }}" - gh release create "$VERSION" \ - --title "Release $VERSION" \ - --notes-file RELEASE_NOTES.md - diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml index d9594b4..325f2ee 100644 --- a/.github/workflows/typescript.yml +++ b/.github/workflows/typescript.yml @@ -2,13 +2,11 @@ name: TypeScript on: push: + branches: + - main pull_request: - -# Superseded runs on the same ref are cancelled (e.g. rapid amend-pushes), -# except on main, where every merge keeps its own CI run -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + release: + types: [published] jobs: detect-packages: @@ -16,7 +14,7 @@ jobs: outputs: packages: ${{ steps.find-packages.outputs.packages }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 - name: Find JS packages id: find-packages working-directory: src @@ -24,42 +22,19 @@ jobs: PACKAGES=$(find . -name package.json -not -path "*/node_modules/*" -exec dirname {} \; | sed 's/^\.\///' | jq -R -s -c 'split("\n")[:-1]') echo "packages=$PACKAGES" >> $GITHUB_OUTPUT - test: - needs: [detect-packages] - strategy: - matrix: - package: ${{ fromJson(needs.detect-packages.outputs.packages) }} - name: Test ${{ matrix.package }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-node@v7 - with: - node-version: 22 - cache: npm - - - name: Install dependencies - working-directory: src/${{ matrix.package }} - run: npm ci - - - name: Run tests - working-directory: src/${{ matrix.package }} - run: npm test --if-present - build: - needs: [detect-packages, test] + needs: [detect-packages] strategy: matrix: package: ${{ fromJson(needs.detect-packages.outputs.packages) }} name: Build ${{ matrix.package }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 - - uses: actions/setup-node@v7 + - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 18 cache: npm - name: Install dependencies @@ -70,3 +45,35 @@ jobs: working-directory: src/${{ matrix.package }} run: npm run build + publish: + runs-on: ubuntu-latest + needs: [build, detect-packages] + if: github.event_name == 'release' + environment: release + + strategy: + matrix: + package: ${{ fromJson(needs.detect-packages.outputs.packages) }} + name: Publish ${{ matrix.package }} + + permissions: + contents: read + id-token: write + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: npm + registry-url: "https://registry.npmjs.org" + + - name: Install dependencies + working-directory: src/${{ matrix.package }} + run: npm ci + + - name: Publish package + working-directory: src/${{ matrix.package }} + run: npm publish # --provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 7c924cf..7ecb710 100644 --- a/.gitignore +++ b/.gitignore @@ -122,10 +122,6 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test -# IDEs -.idea/ -.vscode/ - # yarn v2 .yarn/cache .yarn/unplugged @@ -302,4 +298,3 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -.claude/settings.local.json diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index 5f68642..0000000 --- a/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "mcp-docs": { - "type": "http", - "url": "https://modelcontextprotocol.io/mcp" - } - } -} diff --git a/ADDITIONAL.md b/ADDITIONAL.md deleted file mode 100644 index 3c25b35..0000000 --- a/ADDITIONAL.md +++ /dev/null @@ -1,94 +0,0 @@ -# Additional links - -This is a curated collection of community-built frameworks and resources that simplify building MCP servers and clients, along with other useful tools, libraries, and references for working with MCP. - -## ๐Ÿ“š Frameworks - -### For servers - -* **[Anubis MCP](https://github.com/zoedsoupe/anubis-mcp)** (Elixir) - A high-performance and high-level Model Context Protocol (MCP) implementation in Elixir. Think like "Live View" for MCP. -* **[ModelFetch](https://github.com/phuctm97/modelfetch/)** (TypeScript) - Runtime-agnostic SDK to create and deploy MCP servers anywhere TypeScript/JavaScript runs -* **[EasyMCP](https://github.com/zcaceres/easy-mcp/)** (TypeScript) -* **[FastAPI to MCP auto generator](https://github.com/tadata-org/fastapi_mcp)** โ€“ A zero-configuration tool for automatically exposing FastAPI endpoints as MCP tools by **[Tadata](https://tadata.com/)** -* **[FastMCP](https://github.com/punkpeye/fastmcp)** (TypeScript) -* **[Foobara MCP Connector](https://github.com/foobara/mcp-connector)** - Easily expose Foobara commands written in Ruby as tools via MCP -* **[Foxy Contexts](https://github.com/strowk/foxy-contexts)** โ€“ A library to build MCP servers in Golang by **[strowk](https://github.com/strowk)** -* **[Higress MCP Server Hosting](https://github.com/alibaba/higress/tree/main/plugins/wasm-go/mcp-servers)** - A solution for hosting MCP Servers by extending the API Gateway (based on Envoy) with wasm plugins. -* **[MCP Declarative Java SDK](https://github.com/codeboyzhou/mcp-declarative-java-sdk)** Annotation-driven MCP servers development with Java, no Spring Framework Required, minimize dependencies as much as possible. -* **[MCP-Framework](https://mcp-framework.com)** Build MCP servers with elegance and speed in TypeScript. Comes with a CLI to create your project with `mcp create app`. Get started with your first server in under 5 minutes by **[Alex Andru](https://github.com/QuantGeekDev)** -* **[MCP Plexus](https://github.com/Super-I-Tech/mcp_plexus)**: A secure, **multi-tenant** and Multi-user MCP python server framework built to integrate easily with external services via OAuth 2.1, offering scalable and robust solutions for managing complex AI applications. -* **[mcp_sse (Elixir)](https://github.com/kEND/mcp_sse)** An SSE implementation in Elixir for rapidly creating MCP servers. -* **[mxcp](https://github.com/raw-labs/mxcp)** (Python) - Open-source framework for building enterprise-grade MCP servers using just YAML, SQL, and Python, with built-in auth, monitoring, ETL and policy enforcement. -* **[Next.js MCP Server Template](https://github.com/vercel-labs/mcp-for-next.js)** (Typescript) - A starter Next.js project that uses the MCP Adapter to allow MCP clients to connect and access resources. -* **[PayMCP](https://github.com/blustAI/paymcp)** (Python & TypeScript) - Lightweight payments layer for MCP servers: turn tools into paid endpoints with a two-line decorator. [PyPI](https://pypi.org/project/paymcp/) ยท [npm](https://www.npmjs.com/package/paymcp) ยท [TS repo](https://github.com/blustAI/paymcp-ts) -* **[Perl SDK](https://github.com/mojolicious/mojo-mcp)** - An SDK for building MCP servers and clients with the Perl programming language. -* **[Quarkus MCP Server SDK](https://github.com/quarkiverse/quarkus-mcp-server)** (Java) -- **[R mcptools](https://github.com/posit-dev/mcptools)** - An R SDK for creating R-based MCP servers and retrieving functionality from third-party MCP servers as R functions. -* **[SAP ABAP MCP Server SDK](https://github.com/abap-ai/mcp)** - Build SAP ABAP based MCP servers. ABAP 7.52 based with 7.02 downport; runs on R/3 & S/4HANA on-premises, currently not cloud-ready. -* **[Spring AI MCP Server](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html)** - Provides auto-configuration for setting up an MCP server in Spring Boot applications. -* **[Template MCP Server](https://github.com/mcpdotdirect/template-mcp-server)** - A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure -* **[AgentR Universal MCP SDK](https://github.com/universal-mcp/universal-mcp)** - A python SDK to build MCP Servers with inbuilt credential management by **[Agentr](https://agentr.dev/home)** -* **[Vercel MCP Adapter](https://github.com/vercel/mcp-adapter)** (TypeScript) - A simple package to start serving an MCP server on most major JS meta-frameworks including Next, Nuxt, Svelte, and more. -* **[PHP MCP Server](https://github.com/php-mcp/server)** (PHP) - Core PHP implementation for the Model Context Protocol (MCP) server - -### For clients - -* **[codemirror-mcp](https://github.com/marimo-team/codemirror-mcp)** - CodeMirror extension that implements the Model Context Protocol (MCP) for resource mentions and prompt commands -* **[llm-analysis-assistant](https://github.com/xuzexin-hz/llm-analysis-assistant)** Langfuse Logo - A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also supports monitoring and simulation of ollama/openai interface. -* **[MCP-Agent](https://github.com/lastmile-ai/mcp-agent)** - A simple, composable framework to build agents using Model Context Protocol by **[LastMile AI](https://www.lastmileai.dev)** -* **[Spring AI MCP Client](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html)** - Provides auto-configuration for MCP client functionality in Spring Boot applications. -* **[MCP CLI Client](https://github.com/vincent-pli/mcp-cli-host)** - A CLI host application that enables Large Language Models (LLMs) to interact with external tools through the Model Context Protocol (MCP). -* **[OpenMCP Client](https://github.com/LSTM-Kirigaya/openmcp-client/)** - An all-in-one vscode/trae/cursor plugin for MCP server debugging. [Document](https://kirigaya.cn/openmcp/) & [OpenMCP SDK](https://kirigaya.cn/openmcp/sdk-tutorial/). -* **[PHP MCP Client](https://github.com/php-mcp/client)** - Core PHP implementation for the Model Context Protocol (MCP) Client -* **[Runbear](https://runbear.io/solutions/integrations/slack/mcp)** - No-code MCP client for team chat platforms, such as Slack, Microsoft Teams, and Discord. - -## ๐Ÿ“š Resources - -- **[A2A-MCP Java Bridge](https://github.com/vishalmysore/a2ajava)** - A2AJava brings powerful A2A-MCP integration directly into your Java applications. It enables developers to annotate standard Java methods and instantly expose them as MCP Server, A2A-discoverable actions โ€” with no boilerplate or service registration overhead. -- **[AiMCP](https://www.aimcp.info)** - A collection of MCP clients&servers to find the right mcp tools by **[Hekmon](https://github.com/hekmon8)** -- **[Awesome Crypto MCP Servers by badkk](https://github.com/badkk/awesome-crypto-mcp-servers)** - A curated list of MCP servers by **[Luke Fan](https://github.com/badkk)** -- **[Awesome MCP Servers by appcypher](https://github.com/appcypher/awesome-mcp-servers)** - A curated list of MCP servers by **[Stephen Akinyemi](https://github.com/appcypher)** -- **[Awesome MCP Servers by punkpeye](https://github.com/punkpeye/awesome-mcp-servers)** (**[website](https://glama.ai/mcp/servers)**) - A curated list of MCP servers by **[Frank Fiegel](https://github.com/punkpeye)** -- **[Awesome MCP Servers by wong2](https://github.com/wong2/awesome-mcp-servers)** (**[website](https://mcpservers.org)**) - A curated list of MCP servers by **[wong2](https://github.com/wong2)** -- **[Awesome Remote MCP Servers by JAW9C](https://github.com/jaw9c/awesome-remote-mcp-servers)** - A curated list of **remote** MCP servers, including their authentication support by **[JAW9C](https://github.com/jaw9c)** -- **[Discord Server](https://glama.ai/mcp/discord)** โ€“ A community discord server dedicated to MCP by **[Frank Fiegel](https://github.com/punkpeye)** -- **[Install This MCP](https://installthismcp.com)** - Reduce Installation Friction with beautiful installation guides -- **[Klavis AI](https://www.klavis.ai)** - Open Source MCP Infra. Hosted MCP servers and MCP clients on Slack and Discord. -- **[MCP Badges](https://github.com/mcpx-dev/mcp-badges)** โ€“ Quickly highlight your MCP project with clear, eye-catching badges, by **[Ironben](https://github.com/nanbingxyz)** -- **[MCPProxy](https://github.com/smart-mcp-proxy/mcpproxy-go)** - Open-source local app that enables access to multiple MCP servers and thousands of tools with intelligent discovery via MCP protocol, runs servers in isolated environments, and features automatic quarantine protection against malicious tools. -- **[MCPRepository.com](https://mcprepository.com/)** - A repository that indexes and organizes all MCP servers for easy discovery. -- **[mcp-cli](https://github.com/wong2/mcp-cli)** - A CLI inspector for the Model Context Protocol by **[wong2](https://github.com/wong2)** -- **[mcp-dockmaster](https://mcp-dockmaster.com)** - An Open-Sourced UI to install and manage MCP servers for Windows, Linux and macOS. -- **[mcp-get](https://mcp-get.com)** - Command line tool for installing and managing MCP servers by **[Michael Latman](https://github.com/michaellatman)** -- **[mcp-guardian](https://github.com/eqtylab/mcp-guardian)** - GUI application + tools for proxying / managing control of MCP servers by **[EQTY Lab](https://eqtylab.io)** -- **[MCP Linker](https://github.com/milisp/mcp-linker)** - A cross-platform Tauri GUI tool for one-click setup and management of MCP servers, supporting Claude Desktop, Cursor, Windsurf, VS Code, Cline, and Neovim. -- **[mcp-manager](https://github.com/zueai/mcp-manager)** - Simple Web UI to install and manage MCP servers for Claude Desktop by **[Zue](https://github.com/zueai)** -- **[MCP Marketplace Web Plugin](https://github.com/AI-Agent-Hub/mcp-marketplace)** MCP Marketplace is a small Web UX plugin to integrate with AI applications, Support various MCP Server API Endpoint (e.g pulsemcp.com/deepnlp.org and more). Allowing user to browse, paginate and select various MCP servers by different categories. [Pypi](https://pypi.org/project/mcp-marketplace) | [Maintainer](https://github.com/AI-Agent-Hub) | [Website](http://www.deepnlp.org/store/ai-agent/mcp-server) -- **[mcp.natoma.ai](https://mcp.natoma.ai)** โ€“ A Hosted MCP Platform to discover, install, manage and deploy MCP servers by **[Natoma Labs](https://www.natoma.ai)** -- **[mcp.run](https://mcp.run)** - A hosted registry and control plane to install & run secure + portable MCP Servers. -- **[MCPHub](https://www.mcphub.com)** - Website to list high quality MCP servers and reviews by real users. Also provide online chatbot for popular LLM models with MCP server support. -- **[MCP Router](https://mcp-router.net)** โ€“ Free Windows and macOS app that simplifies MCP management while providing seamless app authentication and powerful log visualization by **[MCP Router](https://github.com/mcp-router/mcp-router)** -- **[MCP Servers Hub](https://github.com/apappascs/mcp-servers-hub)** (**[website](https://mcp-servers-hub-website.pages.dev/)**) - A curated list of MCP servers by **[apappascs](https://github.com/apappascs)** -- **[MCPServers.com](https://mcpservers.com)** - A growing directory of high-quality MCP servers with clear setup guides for a variety of MCP clients. Built by the team behind the **[Highlight MCP client](https://highlightai.com/)** -- **[MCP Servers Rating and User Reviews](http://www.deepnlp.org/store/ai-agent/mcp-server)** - Website to rate MCP servers, write authentic user reviews, and [search engine for agent & mcp](http://www.deepnlp.org/search/agent) -- **[MCP Sky](https://bsky.app/profile/brianell.in/feed/mcp)** - Bluesky feed for MCP related news and discussion by **[@brianell.in](https://bsky.app/profile/brianell.in)** -- **[MCP X Community](https://x.com/i/communities/1861891349609603310)** โ€“ A X community for MCP by **[Xiaoyi](https://x.com/chxy)** -- **[MCPHub](https://github.com/Jeamee/MCPHub-Desktop)** โ€“ An Open Source macOS & Windows GUI Desktop app for discovering, installing and managing MCP servers by **[Jeamee](https://github.com/jeamee)** -- **[mcpm](https://github.com/pathintegral-institute/mcpm.sh)** ([website](https://mcpm.sh)) - MCP Manager (MCPM) is a Homebrew-like service for managing Model Context Protocol (MCP) servers across clients by **[Pathintegral](https://github.com/pathintegral-institute)** -- **[MCPVerse](https://mcpverse.dev)** - A portal for creating & hosting authenticated MCP servers and connecting to them securely. -- **[MCP Servers Search](https://github.com/atonomus/mcp-servers-search)** - An MCP server that provides tools for querying and discovering available MCP servers from this list. -- **[Search MCP Server](https://github.com/krzysztofkucmierz/search-mcp-server)** - Recommends the most relevant MCP servers based on the client's query by searching this README file. -- **[MCPWatch](https://github.com/kapilduraphe/mcp-watch)** - A comprehensive security scanner for Model Context Protocol (MCP) servers that detects vulnerabilities and security issues in your MCP server implementations. -- **[mkinf](https://mkinf.io)** - An Open Source registry of hosted MCP Servers to accelerate AI agent workflows. -- **[Open-Sourced MCP Servers Directory](https://github.com/chatmcp/mcp-directory)** - A curated list of MCP servers by **[mcpso](https://mcp.so)** -- **[OpenTools](https://opentools.com)** - An open registry for finding, installing, and building with MCP servers by **[opentoolsteam](https://github.com/opentoolsteam)** -- **[Programmatic MCP Prototype](https://github.com/domdomegg/programmatic-mcp-prototype)** - Experimental agent prototype demonstrating programmatic MCP tool composition, progressive tool discovery, state persistence, and skill building through TypeScript code execution by **[Adam Jones](https://github.com/domdomegg)** -- **[PulseMCP](https://www.pulsemcp.com)** ([API](https://www.pulsemcp.com/api)) - Community hub & weekly newsletter for discovering MCP servers, clients, articles, and news by **[Tadas Antanavicius](https://github.com/tadasant)**, **[Mike Coughlin](https://github.com/macoughl)**, and **[Ravina Patel](https://github.com/ravinahp)** -- **[r/mcp](https://www.reddit.com/r/mcp)** โ€“ A Reddit community dedicated to MCP by **[Frank Fiegel](https://github.com/punkpeye)** -- **[MCP.ing](https://mcp.ing/)** - A list of MCP services for discovering MCP servers in the community and providing a convenient search function for MCP services by **[iiiusky](https://github.com/iiiusky)** -- **[MCP Hunt](https://mcp-hunt.com)** - Realtime platform for discovering trending MCP servers with momentum tracking, upvoting, and community discussions - like Product Hunt meets Reddit for MCP -- **[Smithery](https://smithery.ai/)** - A registry of MCP servers to find the right tools for your LLM agents by **[Henry Mao](https://github.com/calclavia)** -- **[Toolbase](https://gettoolbase.ai)** - Desktop application that manages tools and MCP servers with just a few clicks - no coding required by **[gching](https://github.com/gching)** -- **[ToolHive](https://github.com/StacklokLabs/toolhive)** - A lightweight utility designed to simplify the deployment and management of MCP servers, ensuring ease of use, consistency, and security through containerization by **[StacklokLabs](https://github.com/StacklokLabs)** -- **[NetMind](https://www.netmind.ai/AIServices)** - Access powerful AI services via simple APIs or MCP servers to supercharge your productivity. -- **[Webrix MCP Gateway](https://github.com/webrix-ai/secure-mcp-gateway)** - Enterprise MCP gateway with SSO, RBAC, audit trails, and token vaults for secure, centralized AI agent access control. Deploy via Helm charts on-premise or in your cloud. [webrix.ai](https://webrix.ai) diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index ad30b91..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,104 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code when working with code in this repository. - -## Project Overview - -Official MCP reference server implementations. This is an npm workspaces monorepo containing 7 servers (4 TypeScript, 3 Python) under `src/`. Each server is a standalone package published to npm or PyPI. - -## Monorepo Structure - -``` -src/ - everything/ TS @modelcontextprotocol/server-everything (reference server, all MCP features) - filesystem/ TS @modelcontextprotocol/server-filesystem (file operations with Roots access control) - memory/ TS @modelcontextprotocol/server-memory (knowledge graph persistence) - sequentialthinking/ TS @modelcontextprotocol/server-sequential-thinking (step-by-step reasoning) - fetch/ Py mcp-server-fetch (web content fetching) - git/ Py mcp-server-git (git repository operations) - time/ Py mcp-server-time (timezone queries and conversion) -``` - -## Build & Test Commands - -### TypeScript servers - -```bash -# Single server -cd src/ && npm ci && npm run build && npm test - -# All TS servers from root -npm install && npm run build -``` - -- Build: `tsc` (target ES2022, module Node16, strict mode) -- Tests: **vitest** with `@vitest/coverage-v8` (required for new tests) -- Node version: **22** - -### Python servers - -```bash -cd src/ && uv sync --frozen --all-extras --dev - -# Run tests (if tests/ or test/ directory exists) -uv run pytest - -# Type checking -uv run pyright - -# Linting -uv run ruff check . -``` - -- Build system: **hatchling** (`uv build`) -- Package manager: **uv** (not pip) -- Python version: **>= 3.10** (per-server `.python-version` file) -- Type checking: **pyright** (enforced in CI) -- Linting: **ruff** - -## Code Style - -### TypeScript - -- ES modules with `.js` extension in import paths -- Strict TypeScript typing for all functions and variables -- Zod schemas for tool input validation -- 2-space indentation, trailing commas in multi-line objects -- camelCase for variables/functions, PascalCase for types/classes, UPPER_CASE for constants -- kebab-case for file names and registered tools/prompts/resources -- Verb-first tool names (e.g., `get-file-info`, not `file-info`) -- Imports grouped: external first, then internal - -### Python - -- Type hints enforced via pyright -- Async/await patterns (especially in fetch server with pytest-asyncio) -- Follow existing module layout per server - -## Contributing Guidelines - -**Accepted:** Bug fixes, usability improvements, enhancements demonstrating MCP protocol features (Resources, Prompts, Roots -- not just Tools). - -**Selective:** New features outside a server's core purpose or highly opinionated additions. - -**Not accepted:** New server implementations (use the [MCP Server Registry](https://github.com/modelcontextprotocol/registry)), README server listing changes. - -## CI/CD Pipeline - -Both TypeScript and Python workflows use **dynamic package detection** (find + jq matrix strategy): - -1. `detect-packages` -- finds all `package.json` / `pyproject.toml` under `src/` -2. `test` -- runs tests per package -3. `build` -- compiles and type-checks per package -4. `publish` -- on release events only (npm for TS, PyPI trusted publishing for Python) - -## MCP Protocol Reference - -The repo is configured with an MCP docs server (`.mcp.json`) pointing to `https://modelcontextprotocol.io/mcp`. For schema details, reference `https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema` which contains versioned schemas in JSON and TypeScript formats. - -## Key Patterns - -- Each server registers capabilities via `registerTools(server)`, `registerResources(server)`, `registerPrompts(server)` functions -- Tool annotations: set `readOnlyHint`, `idempotentHint`, `destructiveHint` per MCP spec -- Transport support: stdio (default), SSE (deprecated), Streamable HTTP -- All PRs are reviewed against the [PR template](.github/pull_request_template.md) checklist -- ensure MCP docs are read, security best practices followed, and changes tested with an LLM client diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ce9a21..026d1aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,40 +1,99 @@ # Contributing to MCP Servers -Thanks for your interest in contributing! Here's how you can help make this repo better. +Thank you for your interest in contributing to the Model Context Protocol (MCP) servers! This document provides guidelines and instructions for contributing. -We accept changes through [the standard GitHub flow model](https://docs.github.com/en/get-started/using-github/github-flow). +## Types of Contributions -## Server Listings +### 1. New Servers +Adding a new server is a valuable way to contribute. Before creating a new server: -The README no longer contains a list of third-party MCP servers โ€” that list has been retired in favor of the [MCP Server Registry](https://github.com/modelcontextprotocol/registry). To make your server discoverable, follow the [quickstart guide](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/quickstart.mdx) to publish it there. +- Check the [modelcontextprotocol.io](https://modelcontextprotocol.io) documentation +- Ensure your server doesn't duplicate existing functionality +- Consider whether your server would be generally useful to others +- Follow [security best practices](https://modelcontextprotocol.io/docs/concepts/transports#security-considerations) from the MCP documentation -You can browse published servers at [https://registry.modelcontextprotocol.io/](https://registry.modelcontextprotocol.io/). +### 2. Improvements to Existing Servers +Enhancements to existing servers are welcome! This includes: -## Server Implementations +- Bug fixes +- Performance improvements +- New features +- Security enhancements -We welcome: -- **Bug fixes** โ€” Help us squash those pesky bugs. -- **Usability improvements** โ€” Making servers easier to use for humans and agents. -- **Enhancements that demonstrate MCP protocol features** โ€” We encourage contributions that help reference servers better illustrate underutilized aspects of the MCP protocol beyond just Tools, such as Resources, Prompts, or Roots. For example, adding Roots support to filesystem-server helps showcase this important but lesser-known feature. +### 3. Documentation +Documentation improvements are always welcome: -We're more selective about: -- **Other new features** โ€” Especially if they're not crucial to the server's core purpose or are highly opinionated. The existing servers are reference servers meant to inspire the community. If you need specific features, we encourage you to build enhanced versions and publish them to the [MCP Server Registry](https://github.com/modelcontextprotocol/registry)! We think a diverse ecosystem of servers is beneficial for everyone. +- Fixing typos or unclear instructions +- Adding examples +- Improving setup instructions +- Adding troubleshooting guides -We don't accept: -- **New server implementations** โ€” We encourage you to publish them to the [MCP Server Registry](https://github.com/modelcontextprotocol/registry) instead. +## Getting Started -## Testing +1. Fork the repository +2. Clone your fork: + ```bash + git clone https://github.com/your-username/servers.git + ``` +3. Add the upstream remote: + ```bash + git remote add upstream https://github.com/modelcontextprotocol/servers.git + ``` +4. Create a branch: + ```bash + git checkout -b my-feature + ``` -When adding or configuring tests for servers implemented in TypeScript, use **vitest** as the test framework. Vitest provides better ESM support, faster test execution, and a more modern testing experience. +## Development Guidelines -## Documentation +### Code Style +- Follow the existing code style in the repository +- Include appropriate type definitions +- Add comments for complex logic -Improvements to existing documentation is welcome - although generally we'd prefer ergonomic improvements than documenting pain points if possible! +### Documentation +- Include a detailed README.md in your server directory +- Document all configuration options +- Provide setup instructions +- Include usage examples -We're more selective about adding wholly new documentation, especially in ways that aren't vendor neutral (e.g. how to run a particular server with a particular client). +### Security +- Follow security best practices +- Implement proper input validation +- Handle errors appropriately +- Document security considerations + +## Submitting Changes + +1. Commit your changes: + ```bash + git add . + git commit -m "Description of changes" + ``` +2. Push to your fork: + ```bash + git push origin my-feature + ``` +3. Create a Pull Request through GitHub + +### Pull Request Guidelines + +- Thoroughly test your changes +- Fill out the pull request template completely +- Link any related issues +- Provide clear description of changes +- Include any necessary documentation updates +- Add screenshots for UI changes +- List any breaking changes ## Community -[Learn how the MCP community communicates](https://modelcontextprotocol.io/community/communication). +- Participate in [GitHub Discussions](https://github.com/orgs/modelcontextprotocol/discussions) +- Follow the [Code of Conduct](CODE_OF_CONDUCT.md) -Thank you for helping make MCP servers better for everyone! \ No newline at end of file +## Questions? + +- Check the [documentation](https://modelcontextprotocol.io) +- Ask in GitHub Discussions + +Thank you for contributing to MCP Servers! diff --git a/LICENSE b/LICENSE index 4a93985..3d48435 100644 --- a/LICENSE +++ b/LICENSE @@ -1,193 +1,6 @@ -The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0. - -Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License. - -No rights beyond those granted by the applicable original license are conveyed for such contributions. - ---- - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright - owner or by an individual or Legal Entity authorized to submit on behalf - of the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - ---- - MIT License -Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC. +Copyright (c) 2024 Anthropic, PBC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -206,11 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -Creative Commons Attribution 4.0 International (CC-BY-4.0) - -Documentation in this project (excluding specifications) is licensed under -CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for -the full license text. diff --git a/README.md b/README.md index 9c388c8..28604b9 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,42 @@ -# Model Context Protocol servers +# MCP servers -This repository is a collection of *reference implementations* for the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP), as well as references to community-built servers and additional resources. +A collection of reference implementations and community-contributed servers for the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP). This repository showcases the versatility and extensibility of MCP, demonstrating how it can be used to give Large Language Models (LLMs) secure, controlled access to tools and data sources. -> [!IMPORTANT] -> If you are looking for a list of MCP servers, you can browse published servers on [the MCP Registry](https://registry.modelcontextprotocol.io/). The repository served by this README is dedicated to housing just the small number of reference servers maintained by the MCP steering group. +Each MCP server is implemented with either the [Typescript MCP SDK](https://github.com/modelcontextprotocol/typescript-sdk) or [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk). -> [!WARNING] -> The servers in this repository are intended as **reference implementations** to demonstrate MCP features and SDK usage. They are meant to serve as educational examples for developers building their own MCP servers, not as production-ready solutions. Developers should evaluate their own security requirements and implement appropriate safeguards based on their specific threat model and use case. +## ๐ŸŒŸ Featured Servers -The servers in this repository showcase the versatility and extensibility of MCP, demonstrating how it can be used to give Large Language Models (LLMs) secure, controlled access to tools and data sources. -Typically, each MCP server is implemented with an MCP SDK: +- **[Filesystem](src/filesystem)** - Secure file operations with configurable access controls +- **[GitHub](src/github)** - Repository management, file operations, and GitHub API integration +- **[GitLab](src/gitlab)** - GitLab API, enabling project management +- **[Git](src/git)** - Tools to read, search, and manipulate Git repositories +- **[Google Drive](src/gdrive)** - File access and search capabilities for Google Drive +- **[PostgreSQL](src/postgres)** - Read-only database access with schema inspection +- **[Sqlite](src/sqlite)** - Database interaction and business intelligence capabilities +- **[Slack](src/slack)** - Channel management and messaging capabilities +- **[Sentry](src/sentry)** - Retrieving and analyzing issues from Sentry.io +- **[Memory](src/memory)** - Knowledge graph-based persistent memory system +- **[Puppeteer](src/puppeteer)** - Browser automation and web scraping +- **[Brave Search](src/brave-search)** - Web and local search using Brave's Search API +- **[Google Maps](src/google-maps)** - Location services, directions, and place details +- **[Fetch](src/fetch)** - Web content fetching and conversion for efficient LLM usage -- [C# MCP SDK](https://github.com/modelcontextprotocol/csharp-sdk) -- [Go MCP SDK](https://github.com/modelcontextprotocol/go-sdk) -- [Java MCP SDK](https://github.com/modelcontextprotocol/java-sdk) -- [Kotlin MCP SDK](https://github.com/modelcontextprotocol/kotlin-sdk) -- [PHP MCP SDK](https://github.com/modelcontextprotocol/php-sdk) -- [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk) -- [Ruby MCP SDK](https://github.com/modelcontextprotocol/ruby-sdk) -- [Rust MCP SDK](https://github.com/modelcontextprotocol/rust-sdk) -- [Swift MCP SDK](https://github.com/modelcontextprotocol/swift-sdk) -- [TypeScript MCP SDK](https://github.com/modelcontextprotocol/typescript-sdk) +## ๐ŸŒŽ Community Servers -## ๐ŸŒŸ Reference Servers - -These servers aim to demonstrate MCP features and the official SDKs. - -- **[Everything](src/everything)** - Reference / test server with prompts, resources, and tools. -- **[Fetch](src/fetch)** - Web content fetching and conversion for efficient LLM usage. -- **[Filesystem](src/filesystem)** - Secure file operations with configurable access controls. -- **[Git](src/git)** - Tools to read, search, and manipulate Git repositories. -- **[Memory](src/memory)** - Knowledge graph-based persistent memory system. -- **[Sequential Thinking](src/sequentialthinking)** - Dynamic and reflective problem-solving through thought sequences. -- **[Time](src/time)** - Time and timezone conversion capabilities. - -### Archived - -The following reference servers are now archived and can be found at [servers-archived](https://github.com/modelcontextprotocol/servers-archived). - -- **[AWS KB Retrieval](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/aws-kb-retrieval-server)** - Retrieval from AWS Knowledge Base using Bedrock Agent Runtime. -- **[Brave Search](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search)** - Web and local search using Brave's Search API. Has been replaced by the [official server](https://github.com/brave/brave-search-mcp-server) ([`@brave/brave-search-mcp-server`](https://www.npmjs.com/package/@brave/brave-search-mcp-server)). -- **[EverArt](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/everart)** - AI image generation using various models. -- **[GitHub](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/github)** - Repository management, file operations, and GitHub API integration. -- **[GitLab](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/gitlab)** - GitLab API, enabling project management. -- **[Google Drive](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/gdrive)** - File access and search capabilities for Google Drive. -- **[Google Maps](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/google-maps)** - Location services, directions, and place details. -- **[PostgreSQL](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/postgres)** - Read-only database access with schema inspection. -- **[Puppeteer](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/puppeteer)** - Browser automation and web scraping. -- **[Redis](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/redis)** - Interact with Redis key-value stores. -- **[Sentry](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/sentry)** - Retrieving and analyzing issues from Sentry.io. -- **[Slack](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/slack)** - Channel management and messaging capabilities. Now maintained by [Zencoder](https://github.com/zencoderai/slack-mcp-server) -- **[SQLite](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/sqlite)** - Database interaction and business intelligence capabilities. +- **[Cloudflare](https://github.com/cloudflare/mcp-server-cloudflare)** - Deploy, configure & interrogate your resources on the Cloudflare developer platform (e.g. Workers/KV/R2/D1) +- **[Raygun](https://github.com/MindscapeHQ/mcp-server-raygun)** - Interact with your crash reporting and real using monitoring data on your Raygun account ## ๐Ÿš€ Getting Started ### Using MCP Servers in this Repository -TypeScript-based servers in this repository can be used directly with `npx`. +Typescript-based servers in this repository can be used directly with `npx`. For example, this will start the [Memory](src/memory) server: ```sh npx -y @modelcontextprotocol/server-memory ``` -Python-based servers in this repository can be used directly with [`uvx`](https://docs.astral.sh/uv/concepts/tools/) or [`pip`](https://pypi.org/project/pip/). `uvx` is recommended for ease of use and setup. +Python-based servers in this repository can be used directly with [`uvx`](https://docs.astral.sh/uv/concepts/tools/) or [`pip`](https://pypi.org/project/pip/). `uvx` is recommended for ease of use and setup. For example, this will start the [Git](src/git) server: ```sh @@ -90,19 +64,6 @@ However, running a server on its own isn't very useful, and should instead be co } ``` -On Windows, wrap `npx` with `cmd /c`: - -```json -{ - "mcpServers": { - "memory": { - "command": "cmd", - "args": ["/c", "npx", "-y", "@modelcontextprotocol/server-memory"] - } - } -} -``` - Additional examples of using the Claude Desktop as an MCP client might look like: ```json @@ -131,31 +92,21 @@ Additional examples of using the Claude Desktop as an MCP client might look like } ``` -On Windows, apply the same wrapper to each `npx`-based entry above by changing `"command"` to `"cmd"` and prepending `"/c", "npx"` to the existing `args`. Leave `uvx` entries unchanged. - ## ๐Ÿ› ๏ธ Creating Your Own Server Interested in creating your own MCP server? Visit the official documentation at [modelcontextprotocol.io](https://modelcontextprotocol.io/introduction) for comprehensive guides, best practices, and technical details on implementing MCP servers. -## ๐Ÿ“š Learn More - -See [ADDITIONAL.md](ADDITIONAL.md) for a curated list of frameworks and resources that simplify building MCP servers and clients. - ## ๐Ÿค Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for information about contributing to this repository. -## ๐Ÿ“ฆ Releasing - -See [RELEASING.md](RELEASING.md) for how packages are published (OIDC trusted publishing from CI โ€” no registry tokens) and how to retry a failed publish. - ## ๐Ÿ”’ Security See [SECURITY.md](SECURITY.md) for reporting security vulnerabilities. ## ๐Ÿ“œ License -This project is licensed under the Apache License, Version 2.0 for new contributions, with existing code under MIT - see the [LICENSE](LICENSE) file for details. +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## ๐Ÿ’ฌ Community diff --git a/RELEASING.md b/RELEASING.md deleted file mode 100644 index fc7056c..0000000 --- a/RELEASING.md +++ /dev/null @@ -1,49 +0,0 @@ -# Releasing - -How the packages in this repository are published, and what to do when a publish fails. - -## How publishing works - -All packages publish exclusively from the [`release.yml`](.github/workflows/release.yml) GitHub Actions workflow, gated by the `release` environment (a required reviewer must approve each deployment). Releases are triggered deliberately by a maintainer via **workflow_dispatch** (Actions โ†’ Release โ†’ Run workflow, or `gh workflow run release.yml`) โ€” there is no scheduled/automatic release. - -**Authentication is OIDC trusted publishing on both registries โ€” there are no registry tokens.** - -- **npm** (TypeScript servers): each `@modelcontextprotocol/*` package is registered on npmjs.com with a [trusted publisher](https://docs.npmjs.com/trusted-publishers) bound to this repository, workflow filename `release.yml`, and environment `release` (the binding is case-sensitive). Packages publish with [provenance attestations](https://docs.npmjs.com/generating-provenance-statements). -- **PyPI** (Python servers): published via [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) using `pypa/gh-action-pypi-publish`. - -A release run: - -1. **Detects changed packages** since the last release tag โ€” a package counts as changed if any `.py`, `.ts`, or `.md` file in its directory changed (READMEs ship inside the published artifacts). -2. **Stamps versions and pushes the release tag** โ€” versions are date-based (CalVer, e.g. `2026.7.4`). -3. **Publishes each changed package as an independent matrix job** (`fail-fast: false` โ€” one package's failure never blocks another). Each job: checkout at the release tag โ†’ install โ†’ double-publish guard โ†’ **run the package's tests** (plus `pyright` for Python) โ†’ build โ†’ publish. The guard differs by registry: the npm job aborts before tests if the version already exists; for PyPI the skip happens at the publish step itself (`skip-existing` on the upload action). -4. **Creates the GitHub release** with generated notes. - -## When a publish fails - -A failed matrix leg means that one package didn't publish; everything that succeeded stays published. - -**Preferred: re-run the failed jobs on the same run.** - -```bash -gh run rerun --failed --repo modelcontextprotocol/servers -``` - -- A re-run is still a `release.yml` run in the `release` environment, so it satisfies the trusted-publisher binding. -- It re-runs only the failed legs, checked out at the original release tag โ€” it publishes exactly the tagged code, and the double-publish guard keeps already-published packages safe. -- It needs a fresh `release` environment approval, and the run must be complete first (approve or reject any pending deployments). -- GitHub's re-run window is ~30 days from the original run, and re-runs execute the *original* workflow snapshot โ€” workflow fixes on `main` don't apply to a re-run. - -**Otherwise: let the next release pick it up.** If the re-run window has closed (or the fix required a workflow change), the failed version simply never exists on that registry โ€” that's benign; npm and PyPI version histories don't need to match. The package publishes at the next version, provided it has a qualifying change (`.py`, `.ts`, or `.md`) since the last release tag. - -**Never:** - -- Publish manually with an npm token or from a laptop โ€” there are no registry tokens, and manual publishes would break the provenance/trust chain. -- Dispatch a fresh `release.yml` run expecting it to retry a failed version โ€” versions are date-granular, so a same-day dispatch collides with the existing tag, and a later dispatch mints a *new* version. Neither retries the failed one. - -## Environment approvals - -The `release` environment's required-reviewer list is configured in the repository settings (Settings โ†’ Environments โ†’ `release`). Reviewer rights come only from that list โ€” repository admin does not confer deployment approval. - ---- - -Planned changes to this process โ€” semver via changesets for the TypeScript packages, publishing triggered by manually-created GitHub Releases โ€” are tracked in [#4463](https://github.com/modelcontextprotocol/servers/issues/4463). This document will be updated when that work merges. diff --git a/SECURITY.md b/SECURITY.md index 2d6cdc2..ea10c02 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,21 +1,10 @@ # Security Policy +Thank you for helping us keep our MCP servers secure. -Thank you for helping keep the Model Context Protocol and its ecosystem secure. +These servers are maintained by [Anthropic](https://www.anthropic.com/) as part of the Model Context Protocol project. -## Important Notice +The security of our systems and user data is Anthropicโ€™s top priority. We appreciate the work of security researchers acting in good faith in identifying and reporting potential vulnerabilities. -The servers in this repository are **reference implementations** intended to demonstrate -MCP features and SDK usage. They serve as educational examples for developers building -their own MCP servers, not as production-ready solutions. +## Vulnerability Disclosure Program -This repository is **not** eligible for security vulnerability reporting. If you discover -a vulnerability in an MCP SDK, please report it in the appropriate SDK repository. - -## Reporting Security Issues in MCP SDKs - -If you discover a security vulnerability in an MCP SDK, please report it through the -[GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) -in the relevant SDK repository. - -Please **do not** report security vulnerabilities through public GitHub issues, discussions, -or pull requests. +Our Vulnerability Program guidelines are defined on our [HackerOne program page](https://hackerone.com/anthropic-vdp). We ask that any validated vulnerability in this functionality be reported through the [submission form](https://hackerone.com/anthropic-vdp/reports/new?type=team&report_type=vulnerability). diff --git a/package-lock.json b/package-lock.json index 632d340..3079d7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,197 +1,172 @@ { "name": "@modelcontextprotocol/servers", - "version": "0.6.2", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/servers", - "version": "0.6.2", - "license": "SEE LICENSE IN LICENSE", + "version": "0.5.1", + "license": "MIT", "workspaces": [ "src/*" ], "dependencies": { + "@modelcontextprotocol/server-brave-search": "*", "@modelcontextprotocol/server-everything": "*", "@modelcontextprotocol/server-filesystem": "*", + "@modelcontextprotocol/server-gdrive": "*", "@modelcontextprotocol/server-memory": "*", - "@modelcontextprotocol/server-sequential-thinking": "*" + "@modelcontextprotocol/server-postgres": "*", + "@modelcontextprotocol/server-puppeteer": "*", + "@modelcontextprotocol/server-slack": "*" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, - "license": "MIT", + "node_modules/@google-cloud/local-auth": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/local-auth/-/local-auth-3.0.1.tgz", + "integrity": "sha512-YJ3GFbksfHyEarbVHPSCzhKpjbnlAhdzg2SEf79l6ODukrSM1qUOqfopY232Xkw26huKSndyzmJz+A6b2WYn7Q==", "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" + "arrify": "^2.0.1", + "google-auth-library": "^9.0.0", + "open": "^7.0.3", + "server-destroy": "^1.0.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, - "license": "MIT", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "license": "MIT", "engines": { - "node": ">=18" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@hono/node-server": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", - "integrity": "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", - "license": "MIT", - "engines": { - "node": ">=20" + "node": ">=12" }, - "peerDependencies": { - "hono": "^4" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", - "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", - "license": "MIT", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.5.0.tgz", + "integrity": "sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==", "dependencies": { - "@hono/node-server": "^1.19.9 || ^2.0.5", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } + "zod": "^3.23.8" } }, + "node_modules/@modelcontextprotocol/server-brave-search": { + "resolved": "src/brave-search", + "link": true + }, "node_modules/@modelcontextprotocol/server-everything": { "resolved": "src/everything", "link": true @@ -200,444 +175,131 @@ "resolved": "src/filesystem", "link": true }, + "node_modules/@modelcontextprotocol/server-gdrive": { + "resolved": "src/gdrive", + "link": true + }, + "node_modules/@modelcontextprotocol/server-github": { + "resolved": "src/github", + "link": true + }, + "node_modules/@modelcontextprotocol/server-gitlab": { + "resolved": "src/gitlab", + "link": true + }, + "node_modules/@modelcontextprotocol/server-google-maps": { + "resolved": "src/google-maps", + "link": true + }, "node_modules/@modelcontextprotocol/server-memory": { "resolved": "src/memory", "link": true }, - "node_modules/@modelcontextprotocol/server-sequential-thinking": { - "resolved": "src/sequentialthinking", + "node_modules/@modelcontextprotocol/server-postgres": { + "resolved": "src/postgres", "link": true }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", - "dev": true, + "node_modules/@modelcontextprotocol/server-puppeteer": { + "resolved": "src/puppeteer", + "link": true + }, + "node_modules/@modelcontextprotocol/server-slack": { + "resolved": "src/slack", + "link": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "license": "MIT", "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "engines": { + "node": ">=14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", + "node_modules/@puppeteer/browsers": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.1.tgz", + "integrity": "sha512-0kdAbmic3J09I6dT8e9vE2JOCSt13wHCW5x/ly8TSt2bDtuIWe2TgLZZDHdcziw9AVCzflMAXCrVyRIhIs44Ng==", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "ms": "^2.1.3" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "node": ">=6.0" }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" }, "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", "dev": true, - "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" } }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*" } }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/diff": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.3.tgz", - "integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" + "@types/qs": "*", + "@types/serve-static": "*" } }, "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -646,452 +308,144 @@ } }, "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true, - "license": "MIT" + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true }, "node_modules/@types/node": { - "version": "22.19.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.21.tgz", - "integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==", - "dev": true, + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/pg": { + "version": "8.11.10", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.10.tgz", + "integrity": "sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" } }, "node_modules/@types/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", - "dev": true, - "license": "MIT" + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", "dev": true, - "license": "MIT", "dependencies": { + "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, - "license": "MIT", "dependencies": { "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { "@types/node": "*" } }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitest/coverage-v8": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", - "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.8", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.1.8", - "vitest": "4.1.8" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.8", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.8", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.8", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" } }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "debug": "^4.3.4" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">= 14" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ajv": "^8.0.0" + "ms": "^2.1.3" }, - "peerDependencies": { - "ajv": "^8.0.0" + "engines": { + "node": ">=6.0" }, "peerDependenciesMeta": { - "ajv": { + "supports-color": { "optional": true } } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/ast-v8-to-istanbul": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz", - "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { + "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/cliui/node_modules/ansi-styles": { + "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1102,60 +456,274 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", + "optional": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", + "node_modules/bare-os": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "optional": true, "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "bare-os": "^2.1.0" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", + "node_modules/bare-stream": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.2.tgz", + "integrity": "sha512-EFZHSIBkDgSHIwj2l2QZfP4U5OcD4xFAOwhSb/vlr9PIqyGJGvB/nfClJbcnh3EY4jtPE4zsb5ztae96bVF79A==", + "optional": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "streamx": "^2.20.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chromium-bidi": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz", + "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1166,77 +734,81 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">= 0.6" } }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">= 0.10" + "node": ">=14" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cross-spawn": { @@ -1253,11 +825,295 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1367902", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { "ms": "^2.1.3" }, @@ -1270,496 +1126,194 @@ } } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "node_modules/extract-zip/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" }, - "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" + "pend": "~1.2.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { + "node_modules/fetch-blob": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", - "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/fastify" + "url": "https://github.com/sponsors/jimmywarting" }, { - "type": "opencollective", - "url": "https://opencollective.com/fastify" + "type": "paypal", + "url": "https://paypal.me/jimmywarting" } ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, "engines": { - "node": ">=8" + "node": "^12.20 || >= 14.13" } }, "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 18.0.0" + "node": ">= 0.8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" } }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=14.14" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -1768,67 +1322,163 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "license": "MIT", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dependencies": { "pump": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/get-uri/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", + "node_modules/google-auth-library": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", + "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", "dependencies": { - "is-glob": "^4.0.1" + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "144.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-144.0.0.tgz", + "integrity": "sha512-ELcWOXtJxjPX4vsKMh+7V+jZvgPwYMlEhQFiu2sa9Qmt5veX8nwXPksOWGGN6Zk4xCiLygUyaz7xGtcMO+Onxw==", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" } }, "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -1836,21 +1486,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -1859,10 +1498,9 @@ } }, "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -1870,85 +1508,165 @@ "node": ">= 0.4" } }, - "node_modules/hono": { - "version": "4.12.32", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz", - "integrity": "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "license": "MIT", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, "engines": { "node": ">= 12" } @@ -1957,19 +1675,22 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", "engines": { "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, - "license": "MIT", "dependencies": { - "hasown": "^2.0.3" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -1978,577 +1699,212 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dependencies": { - "is-extglob": "^2.1.1" + "is-docker": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "license": "MIT", "funding": { - "url": "https://github.com/sponsors/panva" + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, - "node_modules/json-schema-traverse": { + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/json-bigint": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" + "bignumber.js": "^9.0.0" } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "universalify": "^2.0.0" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "graceful-fs": "^4.1.6" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "license": "BlueOak-1.0.0", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/magicast": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", - "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.3", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "node": ">=12" } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "engines": { - "node": ">= 8" + "node": ">= 0.6" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=4" } }, "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "^1.54.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "license": "BlueOak-1.0.0", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minimist": { @@ -2556,98 +1912,87 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" }, - "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/npm-run-path": { + "node_modules/netmask": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { - "path-key": "^2.0.0" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "engines": { "node": ">= 0.4" }, @@ -2655,25 +2000,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obug": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT", - "engines": { - "node": ">=12.20.0" - } + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -2685,36 +2021,127 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", "dependencies": { "wrappy": "1" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, - "license": "MIT", + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2728,127 +2155,245 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/pg": { + "version": "8.13.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz", + "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==", + "dependencies": { + "pg-connection-string": "^2.7.0", + "pg-pool": "^3.7.0", + "pg-protocol": "^1.7.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } } }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", "dev": true, - "license": "MIT" + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-pool": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz", + "integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz", + "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==" + }, + "node_modules/pg-types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz", + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "dev": true, + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pg/node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg/node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg/node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pg/node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/postcss": { - "version": "8.5.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "obuf": "~1.1.2" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">= 6" } }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" - }, "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "node": ">=12" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" + "node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -2857,24 +2402,122 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, - "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "license": "BSD-3-Clause", + "node_modules/puppeteer": { + "version": "23.8.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.8.0.tgz", + "integrity": "sha512-MFWDMWoCcOpwNwQIjA9gPKWrEUbj8bLCzkK56w5lZPMUT6wK4FfpgOEPxKffVmXEMYMZzgcjxzqy15b/Q1ibaw==", + "hasInstallScript": true, "dependencies": { - "side-channel": "^1.1.0" + "@puppeteer/browsers": "2.4.1", + "chromium-bidi": "0.8.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1367902", + "puppeteer-core": "23.8.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "23.8.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.8.0.tgz", + "integrity": "sha512-c2ymGN2M//We7pC+JhP2dE/g4+qnT89BO+EMSZyJmecN3DN6RNqErA7eH7DrWoNIcU75r2nP4VHa4pswAL6NVg==", + "dependencies": { + "@puppeteer/browsers": "2.4.1", + "chromium-bidi": "0.8.0", + "debug": "^4.3.7", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -2883,64 +2526,42 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.8" } }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/rechoir": { @@ -2959,108 +2580,39 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, - "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.133.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -3074,30 +2626,17 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" + ] }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -3106,61 +2645,80 @@ } }, "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.8.0" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -3184,14 +2742,12 @@ } }, "node_modules/shelljs": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.9.2.tgz", - "integrity": "sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "execa": "^1.0.0", - "fast-glob": "^3.3.2", + "glob": "^7.0.0", "interpret": "^1.0.0", "rechoir": "^0.6.2" }, @@ -3199,37 +2755,34 @@ "shjs": "bin/shjs" }, "engines": { - "node": ">=18" + "node": ">=4" } }, "node_modules/shx": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/shx/-/shx-0.4.0.tgz", - "integrity": "sha512-Z0KixSIlGPpijKgcH6oCMCbltPImvaKy0sGH8AkLRXw1KyzpKtaCTizP2xen+hNDqVF4xxgvA0KXSb9o4Q6hnA==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", "dev": true, - "license": "MIT", "dependencies": { - "minimist": "^1.2.8", - "shelljs": "^0.9.2" + "minimist": "^1.2.3", + "shelljs": "^0.8.5" }, "bin": { "shx": "lib/cli.js" }, "engines": { - "node": ">=18" + "node": ">=6" } }, "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { + "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" }, "engines": { "node": ">= 0.4" @@ -3238,133 +2791,164 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { "node": ">= 0.8" } }, - "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", + "node_modules/streamx": { + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.2.tgz", + "integrity": "sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==", "dependencies": { - "safe-buffer": "~5.1.0" + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "dev": true, - "license": "MIT", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" @@ -3375,7 +2959,6 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3383,117 +2966,79 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, + "node_modules/text-decoder": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", + "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", "engines": { "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.6" } }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==" }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3502,203 +3047,95 @@ "node": ">=14.17" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", - "dev": true, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "node": ">= 8" } }, - "node_modules/vitest": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", - "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", - "dev": true, - "license": "MIT", + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dependencies": { - "@vitest/expect": "4.1.8", - "@vitest/mocker": "4.1.8", - "@vitest/pretty-format": "4.1.8", - "@vitest/runner": "4.1.8", - "@vitest/snapshot": "4.1.8", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.8", - "@vitest/browser-preview": "4.1.8", - "@vitest/browser-webdriverio": "4.1.8", - "@vitest/coverage-istanbul": "4.1.8", - "@vitest/coverage-v8": "4.1.8", - "@vitest/ui": "4.1.8", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, "node_modules/which": { @@ -3716,34 +3153,77 @@ "node": ">= 8" } }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", "engines": { "node": ">=10" } @@ -3752,7 +3232,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -3770,157 +3249,460 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", "engines": { "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "license": "MIT", + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz", + "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==", "peerDependencies": { - "zod": "^3.25.28 || ^4" + "zod": "^3.23.3" + } + }, + "src/brave-search": { + "name": "@modelcontextprotocol/server-brave-search", + "version": "0.5.2", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.5.0" + }, + "bin": { + "mcp-server-brave-search": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "src/brave-search/node_modules/@types/node": { + "version": "20.17.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz", + "integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "src/duckduckgo": { + "name": "@modelcontextprotocol/server-duckduckgo", + "version": "0.2.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.5.0", + "jsdom": "^24.1.3", + "node-fetch": "^3.3.2" + }, + "bin": { + "mcp-server-duckduckgo": "dist/index.js" + }, + "devDependencies": { + "@types/jsdom": "^21.1.6", + "@types/node": "^20.10.0", + "shx": "^0.3.4", + "typescript": "^5.6.2" } }, "src/everything": { "name": "@modelcontextprotocol/server-everything", - "version": "2.0.0", - "license": "SEE LICENSE IN LICENSE", + "version": "0.5.1", + "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "cors": "^2.8.5", - "express": "^5.2.1", - "jszip": "^3.10.1", - "zod": "^4.0.0" + "@modelcontextprotocol/sdk": "0.5.0", + "express": "^4.21.1", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.23.5" }, "bin": { "mcp-server-everything": "dist/index.js" }, "devDependencies": { - "@types/cors": "^2.8.19", - "@types/express": "^5.0.6", - "@vitest/coverage-v8": "^4.1.8", - "prettier": "^2.8.8", - "shx": "^0.4.0", - "typescript": "^5.6.2", - "vitest": "^4.1.8" + "@types/express": "^5.0.0", + "shx": "^0.3.4", + "typescript": "^5.6.2" } }, "src/filesystem": { "name": "@modelcontextprotocol/server-filesystem", - "version": "0.6.3", - "license": "SEE LICENSE IN LICENSE", + "version": "0.5.1", + "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "diff": "^8.0.3", - "glob": "^13.0.6", - "minimatch": "^10.0.1", - "zod": "^4.0.0" + "@modelcontextprotocol/sdk": "0.5.0", + "glob": "^10.3.10", + "zod-to-json-schema": "^3.23.5" }, "bin": { "mcp-server-filesystem": "dist/index.js" }, "devDependencies": { - "@types/diff": "^5.0.9", - "@types/minimatch": "^5.1.2", - "@types/node": "^22", - "@vitest/coverage-v8": "^4.1.8", - "shx": "^0.4.0", - "typescript": "^5.8.2", - "vitest": "^4.1.8" + "@types/node": "^20.11.0", + "shx": "^0.3.4", + "typescript": "^5.3.3" + } + }, + "src/filesystem/node_modules/@types/node": { + "version": "20.17.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz", + "integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "src/filesystem/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "src/filesystem/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "src/filesystem/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "src/gdrive": { + "name": "@modelcontextprotocol/server-gdrive", + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@google-cloud/local-auth": "^3.0.1", + "@modelcontextprotocol/sdk": "0.5.0", + "googleapis": "^144.0.0" + }, + "bin": { + "mcp-server-gdrive": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^22.9.3", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "src/gdrive/node_modules/@types/node": { + "version": "22.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.3.tgz", + "integrity": "sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "src/github": { + "name": "@modelcontextprotocol/server-github", + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2", + "zod-to-json-schema": "^3.23.5" + }, + "bin": { + "mcp-server-github": "dist/index.js" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "src/github/node_modules/@modelcontextprotocol/sdk": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", + "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "src/github/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "src/github/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "src/gitlab": { + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2", + "zod-to-json-schema": "^3.23.5" + }, + "bin": { + "mcp-server-gitlab": "dist/index.js" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "src/gitlab/node_modules/@modelcontextprotocol/sdk": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", + "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "src/gitlab/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "src/gitlab/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "src/google-maps": { + "name": "@modelcontextprotocol/server-google-maps", + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2" + }, + "bin": { + "mcp-server-google-maps": "dist/index.js" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "src/google-maps/node_modules/@modelcontextprotocol/sdk": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", + "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "src/google-maps/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "src/google-maps/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "src/memory": { "name": "@modelcontextprotocol/server-memory", - "version": "0.6.3", - "license": "SEE LICENSE IN LICENSE", + "version": "0.5.1", + "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "zod": "^4.0.0" + "@modelcontextprotocol/sdk": "0.5.0" }, "bin": { "mcp-server-memory": "dist/index.js" }, "devDependencies": { - "@types/node": "^22", - "@vitest/coverage-v8": "^4.1.8", - "shx": "^0.4.0", - "typescript": "^5.6.2", - "vitest": "^4.1.8" + "@types/node": "^22.9.3", + "shx": "^0.3.4", + "typescript": "^5.6.2" } }, - "src/sequentialthinking": { - "name": "@modelcontextprotocol/server-sequential-thinking", - "version": "0.6.2", - "license": "SEE LICENSE IN LICENSE", + "src/memory/node_modules/@types/node": { + "version": "22.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.3.tgz", + "integrity": "sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==", + "dev": true, "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "chalk": "^5.3.0", - "yargs": "^17.7.2", - "zod": "^4.0.0" + "undici-types": "~6.19.8" + } + }, + "src/postgres": { + "name": "@modelcontextprotocol/server-postgres", + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "pg": "^8.13.0" }, "bin": { - "mcp-server-sequential-thinking": "dist/index.js" + "mcp-server-postgres": "dist/index.js" }, "devDependencies": { - "@types/node": "^22", - "@types/yargs": "^17.0.32", - "@vitest/coverage-v8": "^4.1.8", - "shx": "^0.4.0", - "typescript": "^5.3.3", - "vitest": "^4.1.8" + "@types/pg": "^8.11.10", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "src/postgres/node_modules/@modelcontextprotocol/sdk": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", + "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "src/puppeteer": { + "name": "@modelcontextprotocol/server-puppeteer", + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.5.0", + "puppeteer": "^23.4.0" + }, + "bin": { + "mcp-server-puppeteer": "dist/index.js" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "src/slack": { + "name": "@modelcontextprotocol/server-slack", + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0" + }, + "bin": { + "mcp-server-slack": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^22.9.3", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "src/slack/node_modules/@modelcontextprotocol/sdk": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", + "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "src/slack/node_modules/@types/node": { + "version": "22.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.3.tgz", + "integrity": "sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.8" } } } diff --git a/package.json b/package.json index ab0b8c8..19240a5 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "@modelcontextprotocol/servers", "private": true, - "version": "0.6.2", + "version": "0.5.1", "description": "Model Context Protocol servers", - "license": "SEE LICENSE IN LICENSE", - "author": "Model Context Protocol a Series of LF Projects, LLC.", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", "homepage": "https://modelcontextprotocol.io", "bugs": "https://github.com/modelcontextprotocol/servers/issues", "type": "module", @@ -20,12 +20,12 @@ }, "dependencies": { "@modelcontextprotocol/server-everything": "*", + "@modelcontextprotocol/server-gdrive": "*", + "@modelcontextprotocol/server-postgres": "*", + "@modelcontextprotocol/server-puppeteer": "*", + "@modelcontextprotocol/server-slack": "*", + "@modelcontextprotocol/server-brave-search": "*", "@modelcontextprotocol/server-memory": "*", - "@modelcontextprotocol/server-filesystem": "*", - "@modelcontextprotocol/server-sequential-thinking": "*" - }, - "overrides": { - "qs": ">=6.15.2", - "hono": ">=4.12.21" + "@modelcontextprotocol/server-filesystem": "*" } } diff --git a/scripts/release.py b/scripts/release.py deleted file mode 100755 index b3dcf0c..0000000 --- a/scripts/release.py +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env uv run --script -# /// script -# requires-python = ">=3.12" -# dependencies = [ -# "click>=8.1.8", -# "tomlkit>=0.13.2" -# ] -# /// -import sys -import re -import click -from pathlib import Path -import json -import tomlkit -import datetime -import subprocess -from dataclasses import dataclass -from typing import Any, Iterator, NewType, Protocol - - -Version = NewType("Version", str) -GitHash = NewType("GitHash", str) - - -class GitHashParamType(click.ParamType): - name = "git_hash" - - def convert( - self, value: Any, param: click.Parameter | None, ctx: click.Context | None - ) -> GitHash | None: - if value is None: - return None - - if not (8 <= len(value) <= 40): - self.fail(f"Git hash must be between 8 and 40 characters, got {len(value)}") - - if not re.match(r"^[0-9a-fA-F]+$", value): - self.fail("Git hash must contain only hex digits (0-9, a-f)") - - try: - # Verify hash exists in repo - subprocess.run( - ["git", "rev-parse", "--verify", value], check=True, capture_output=True - ) - except subprocess.CalledProcessError: - self.fail(f"Git hash {value} not found in repository") - - return GitHash(value.lower()) - - -GIT_HASH = GitHashParamType() - - -class Package(Protocol): - path: Path - - def package_name(self) -> str: ... - - def update_version(self, version: Version) -> None: ... - - -@dataclass -class NpmPackage: - path: Path - - def package_name(self) -> str: - with open(self.path / "package.json", "r") as f: - return json.load(f)["name"] - - def update_version(self, version: Version): - with open(self.path / "package.json", "r+") as f: - data = json.load(f) - data["version"] = version - f.seek(0) - json.dump(data, f, indent=2) - f.truncate() - - -@dataclass -class PyPiPackage: - path: Path - - def package_name(self) -> str: - with open(self.path / "pyproject.toml") as f: - toml_data = tomlkit.parse(f.read()) - name = toml_data.get("project", {}).get("name") - if not name: - raise Exception("No name in pyproject.toml project section") - return str(name) - - def update_version(self, version: Version): - # Update version in pyproject.toml - with open(self.path / "pyproject.toml") as f: - data = tomlkit.parse(f.read()) - data["project"]["version"] = version - - with open(self.path / "pyproject.toml", "w") as f: - f.write(tomlkit.dumps(data)) - - # Regenerate uv.lock to match the updated pyproject.toml - subprocess.run(["uv", "lock"], cwd=self.path, check=True) - - -def has_changes(path: Path, git_hash: GitHash) -> bool: - """Check if any files changed between current state and git hash""" - try: - output = subprocess.run( - ["git", "diff", "--name-only", git_hash, "--", "."], - cwd=path, - check=True, - capture_output=True, - text=True, - ) - - changed_files = [Path(f) for f in output.stdout.splitlines()] - # .md counts as a change: READMEs ship inside the published package - # (npm tarball / PyPI long_description) - relevant_files = [f for f in changed_files if f.suffix in [".py", ".ts", ".md"]] - return len(relevant_files) >= 1 - except subprocess.CalledProcessError: - return False - - -def gen_version() -> Version: - """Generate version based on current date""" - now = datetime.datetime.now() - return Version(f"{now.year}.{now.month}.{now.day}") - - -def find_changed_packages(directory: Path, git_hash: GitHash) -> Iterator[Package]: - for path in directory.glob("*/package.json"): - if has_changes(path.parent, git_hash): - yield NpmPackage(path.parent) - for path in directory.glob("*/pyproject.toml"): - if has_changes(path.parent, git_hash): - yield PyPiPackage(path.parent) - - -@click.group() -def cli(): - pass - - -@cli.command("update-packages") -@click.option( - "--directory", type=click.Path(exists=True, path_type=Path), default=Path.cwd() -) -@click.argument("git_hash", type=GIT_HASH) -def update_packages(directory: Path, git_hash: GitHash) -> int: - # Detect package type - path = directory.resolve(strict=True) - version = gen_version() - - for package in find_changed_packages(path, git_hash): - name = package.package_name() - package.update_version(version) - - click.echo(f"{name}@{version}") - - return 0 - - -@cli.command("generate-notes") -@click.option( - "--directory", type=click.Path(exists=True, path_type=Path), default=Path.cwd() -) -@click.argument("git_hash", type=GIT_HASH) -def generate_notes(directory: Path, git_hash: GitHash) -> int: - # Detect package type - path = directory.resolve(strict=True) - version = gen_version() - - click.echo(f"# Release : v{version}") - click.echo("") - click.echo("## Updated packages") - for package in find_changed_packages(path, git_hash): - name = package.package_name() - click.echo(f"- {name}@{version}") - - return 0 - - -@cli.command("generate-version") -def generate_version() -> int: - # Detect package type - click.echo(gen_version()) - return 0 - - -@cli.command("generate-matrix") -@click.option( - "--directory", type=click.Path(exists=True, path_type=Path), default=Path.cwd() -) -@click.option("--npm", is_flag=True, default=False) -@click.option("--pypi", is_flag=True, default=False) -@click.argument("git_hash", type=GIT_HASH) -def generate_matrix(directory: Path, git_hash: GitHash, pypi: bool, npm: bool) -> int: - # Detect package type - path = directory.resolve(strict=True) - version = gen_version() - - changes = [] - for package in find_changed_packages(path, git_hash): - pkg = package.path.relative_to(path) - if npm and isinstance(package, NpmPackage): - changes.append(str(pkg)) - if pypi and isinstance(package, PyPiPackage): - changes.append(str(pkg)) - - click.echo(json.dumps(changes)) - return 0 - - -if __name__ == "__main__": - sys.exit(cli()) diff --git a/src/brave-search/README.md b/src/brave-search/README.md new file mode 100644 index 0000000..2d5ae0d --- /dev/null +++ b/src/brave-search/README.md @@ -0,0 +1,58 @@ +# Brave Search MCP Server + +An MCP server implementation that integrates the Brave Search API, providing both web and local search capabilities. + +## Features + +- **Web Search**: General queries, news, articles, with pagination and freshness controls +- **Local Search**: Find businesses, restaurants, and services with detailed information +- **Flexible Filtering**: Control result types, safety levels, and content freshness +- **Smart Fallbacks**: Local search automatically falls back to web when no results are found + +## Tools + +- **brave_web_search** + - Execute web searches with pagination and filtering + - Inputs: + - `query` (string): Search terms + - `count` (number, optional): Results per page (max 20) + - `offset` (number, optional): Pagination offset (max 9) + +- **brave_local_search** + - Search for local businesses and services + - Inputs: + - `query` (string): Local search terms + - `count` (number, optional): Number of results (max 20) + - Automatically falls back to web search if no local results found + + +## Configuration + +### Getting an API Key +1. Sign up for a [Brave Search API account](https://brave.com/search/api/) +2. Choose a plan (Free tier available with 2,000 queries/month) +3. Generate your API key [from the developer dashboard](https://api.search.brave.com/app/keys) + +### Usage with Claude Desktop +Add this to your `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "brave-search": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-brave-search" + ], + "env": { + "BRAVE_API_KEY": "YOUR_API_KEY_HERE" + } + } + } +} +``` + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/brave-search/index.ts b/src/brave-search/index.ts new file mode 100644 index 0000000..e0f6162 --- /dev/null +++ b/src/brave-search/index.ts @@ -0,0 +1,376 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + Tool, +} from "@modelcontextprotocol/sdk/types.js"; + +const WEB_SEARCH_TOOL: Tool = { + name: "brave_web_search", + description: + "Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. " + + "Use this for broad information gathering, recent events, or when you need diverse web sources. " + + "Supports pagination, content filtering, and freshness controls. " + + "Maximum 20 results per request, with offset for pagination. ", + inputSchema: { + type: "object", + properties: { + query: { + type: "string", + description: "Search query (max 400 chars, 50 words)" + }, + count: { + type: "number", + description: "Number of results (1-20, default 10)", + default: 10 + }, + offset: { + type: "number", + description: "Pagination offset (max 9, default 0)", + default: 0 + }, + }, + required: ["query"], + }, +}; + +const LOCAL_SEARCH_TOOL: Tool = { + name: "brave_local_search", + description: + "Searches for local businesses and places using Brave's Local Search API. " + + "Best for queries related to physical locations, businesses, restaurants, services, etc. " + + "Returns detailed information including:\n" + + "- Business names and addresses\n" + + "- Ratings and review counts\n" + + "- Phone numbers and opening hours\n" + + "Use this when the query implies 'near me' or mentions specific locations. " + + "Automatically falls back to web search if no local results are found.", + inputSchema: { + type: "object", + properties: { + query: { + type: "string", + description: "Local search query (e.g. 'pizza near Central Park')" + }, + count: { + type: "number", + description: "Number of results (1-20, default 5)", + default: 5 + }, + }, + required: ["query"] + } +}; + +// Server implementation +const server = new Server( + { + name: "example-servers/brave-search", + version: "0.1.0", + }, + { + capabilities: { + tools: {}, + }, + }, +); + +// Check for API key +const BRAVE_API_KEY = process.env.BRAVE_API_KEY!; +if (!BRAVE_API_KEY) { + console.error("Error: BRAVE_API_KEY environment variable is required"); + process.exit(1); +} + +const RATE_LIMIT = { + perSecond: 1, + perMonth: 15000 +}; + +let requestCount = { + second: 0, + month: 0, + lastReset: Date.now() +}; + +function checkRateLimit() { + const now = Date.now(); + if (now - requestCount.lastReset > 1000) { + requestCount.second = 0; + requestCount.lastReset = now; + } + if (requestCount.second >= RATE_LIMIT.perSecond || + requestCount.month >= RATE_LIMIT.perMonth) { + throw new Error('Rate limit exceeded'); + } + requestCount.second++; + requestCount.month++; +} + +interface BraveWeb { + web?: { + results?: Array<{ + title: string; + description: string; + url: string; + language?: string; + published?: string; + rank?: number; + }>; + }; + locations?: { + results?: Array<{ + id: string; // Required by API + title?: string; + }>; + }; +} + +interface BraveLocation { + id: string; + name: string; + address: { + streetAddress?: string; + addressLocality?: string; + addressRegion?: string; + postalCode?: string; + }; + coordinates?: { + latitude: number; + longitude: number; + }; + phone?: string; + rating?: { + ratingValue?: number; + ratingCount?: number; + }; + openingHours?: string[]; + priceRange?: string; +} + +interface BravePoiResponse { + results: BraveLocation[]; +} + +interface BraveDescription { + descriptions: {[id: string]: string}; +} + +function isBraveWebSearchArgs(args: unknown): args is { query: string; count?: number } { + return ( + typeof args === "object" && + args !== null && + "query" in args && + typeof (args as { query: string }).query === "string" + ); +} + +function isBraveLocalSearchArgs(args: unknown): args is { query: string; count?: number } { + return ( + typeof args === "object" && + args !== null && + "query" in args && + typeof (args as { query: string }).query === "string" + ); +} + +async function performWebSearch(query: string, count: number = 10, offset: number = 0) { + checkRateLimit(); + const url = new URL('https://api.search.brave.com/res/v1/web/search'); + url.searchParams.set('q', query); + url.searchParams.set('count', Math.min(count, 20).toString()); // API limit + url.searchParams.set('offset', offset.toString()); + + const response = await fetch(url, { + headers: { + 'Accept': 'application/json', + 'Accept-Encoding': 'gzip', + 'X-Subscription-Token': BRAVE_API_KEY + } + }); + + if (!response.ok) { + throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`); + } + + const data = await response.json() as BraveWeb; + + // Extract just web results + const results = (data.web?.results || []).map(result => ({ + title: result.title || '', + description: result.description || '', + url: result.url || '' + })); + + return results.map(r => + `Title: ${r.title}\nDescription: ${r.description}\nURL: ${r.url}` + ).join('\n\n'); +} + +async function performLocalSearch(query: string, count: number = 5) { + checkRateLimit(); + // Initial search to get location IDs + const webUrl = new URL('https://api.search.brave.com/res/v1/web/search'); + webUrl.searchParams.set('q', query); + webUrl.searchParams.set('search_lang', 'en'); + webUrl.searchParams.set('result_filter', 'locations'); + webUrl.searchParams.set('count', Math.min(count, 20).toString()); + + const webResponse = await fetch(webUrl, { + headers: { + 'Accept': 'application/json', + 'Accept-Encoding': 'gzip', + 'X-Subscription-Token': BRAVE_API_KEY + } + }); + + if (!webResponse.ok) { + throw new Error(`Brave API error: ${webResponse.status} ${webResponse.statusText}\n${await webResponse.text()}`); + } + + const webData = await webResponse.json() as BraveWeb; + const locationIds = webData.locations?.results?.filter((r): r is {id: string; title?: string} => r.id != null).map(r => r.id) || []; + + if (locationIds.length === 0) { + return performWebSearch(query, count); // Fallback to web search + } + + // Get POI details and descriptions in parallel + const [poisData, descriptionsData] = await Promise.all([ + getPoisData(locationIds), + getDescriptionsData(locationIds) + ]); + + return formatLocalResults(poisData, descriptionsData); +} + +async function getPoisData(ids: string[]): Promise { + checkRateLimit(); + const url = new URL('https://api.search.brave.com/res/v1/local/pois'); + ids.filter(Boolean).forEach(id => url.searchParams.append('ids', id)); + const response = await fetch(url, { + headers: { + 'Accept': 'application/json', + 'Accept-Encoding': 'gzip', + 'X-Subscription-Token': BRAVE_API_KEY + } + }); + + if (!response.ok) { + throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`); + } + + const poisResponse = await response.json() as BravePoiResponse; + return poisResponse; +} + +async function getDescriptionsData(ids: string[]): Promise { + checkRateLimit(); + const url = new URL('https://api.search.brave.com/res/v1/local/descriptions'); + ids.filter(Boolean).forEach(id => url.searchParams.append('ids', id)); + const response = await fetch(url, { + headers: { + 'Accept': 'application/json', + 'Accept-Encoding': 'gzip', + 'X-Subscription-Token': BRAVE_API_KEY + } + }); + + if (!response.ok) { + throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`); + } + + const descriptionsData = await response.json() as BraveDescription; + return descriptionsData; +} + +function formatLocalResults(poisData: BravePoiResponse, descData: BraveDescription): string { + return (poisData.results || []).map(poi => { + const address = [ + poi.address?.streetAddress ?? '', + poi.address?.addressLocality ?? '', + poi.address?.addressRegion ?? '', + poi.address?.postalCode ?? '' + ].filter(part => part !== '').join(', ') || 'N/A'; + + return `Name: ${poi.name} +Address: ${address} +Phone: ${poi.phone || 'N/A'} +Rating: ${poi.rating?.ratingValue ?? 'N/A'} (${poi.rating?.ratingCount ?? 0} reviews) +Price Range: ${poi.priceRange || 'N/A'} +Hours: ${(poi.openingHours || []).join(', ') || 'N/A'} +Description: ${descData.descriptions[poi.id] || 'No description available'} +`; + }).join('\n---\n') || 'No local results found'; +} + +// Tool handlers +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [WEB_SEARCH_TOOL, LOCAL_SEARCH_TOOL], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + try { + const { name, arguments: args } = request.params; + + if (!args) { + throw new Error("No arguments provided"); + } + + switch (name) { + case "brave_web_search": { + if (!isBraveWebSearchArgs(args)) { + throw new Error("Invalid arguments for brave_web_search"); + } + const { query, count = 10 } = args; + const results = await performWebSearch(query, count); + return { + content: [{ type: "text", text: results }], + isError: false, + }; + } + + case "brave_local_search": { + if (!isBraveLocalSearchArgs(args)) { + throw new Error("Invalid arguments for brave_local_search"); + } + const { query, count = 5 } = args; + const results = await performLocalSearch(query, count); + return { + content: [{ type: "text", text: results }], + isError: false, + }; + } + + default: + return { + content: [{ type: "text", text: `Unknown tool: ${name}` }], + isError: true, + }; + } + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error: ${error instanceof Error ? error.message : String(error)}`, + }, + ], + isError: true, + }; + } +}); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("Brave Search MCP Server running on stdio"); +} + +runServer().catch((error) => { + console.error("Fatal error running server:", error); + process.exit(1); +}); diff --git a/src/brave-search/package.json b/src/brave-search/package.json new file mode 100644 index 0000000..3e68d35 --- /dev/null +++ b/src/brave-search/package.json @@ -0,0 +1,29 @@ +{ + "name": "@modelcontextprotocol/server-brave-search", + "version": "0.5.2", + "description": "MCP server for Brave Search API integration", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-brave-search": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "0.5.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/brave-search/tsconfig.json b/src/brave-search/tsconfig.json new file mode 100644 index 0000000..087f641 --- /dev/null +++ b/src/brave-search/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] + } diff --git a/src/everything/.prettierignore b/src/everything/.prettierignore deleted file mode 100644 index b6ce559..0000000 --- a/src/everything/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -packages -dist -README.md -node_modules diff --git a/src/everything/AGENTS.md b/src/everything/AGENTS.md deleted file mode 100644 index c4a6df1..0000000 --- a/src/everything/AGENTS.md +++ /dev/null @@ -1,52 +0,0 @@ -# MCP "Everything" Server - Development Guidelines - -## Build, Test & Run Commands - -- Build: `npm run build` - Compiles TypeScript to JavaScript -- Watch mode: `npm run watch` - Watches for changes and rebuilds automatically -- Run STDIO server: `npm run start:stdio` - Starts the MCP server using stdio transport -- Run SSE server: `npm run start:sse` - Starts the MCP server with SSE transport -- Run StreamableHttp server: `npm run start:streamableHttp` - Starts the MCP server with StreamableHttp transport -- Prepare release: `npm run prepare` - Builds the project for publishing - -## Code Style Guidelines - -- Use ES modules with `.js` extension in import paths -- Strictly type all functions and variables with TypeScript -- Follow zod schema patterns for tool input validation -- Prefer async/await over callbacks and Promise chains -- Place all imports at top of file, grouped by external then internal -- Use descriptive variable names that clearly indicate purpose -- Implement proper cleanup for timers and resources in server shutdown -- Handle errors with try/catch blocks and provide clear error messages -- Use consistent indentation (2 spaces) and trailing commas in multi-line objects -- Match existing code style, import order, and module layout in the respective folder. -- Use camelCase for variables/functions, -- Use PascalCase for types/classes, -- Use UPPER_CASE for constants -- Use kebab-case for file names and registered tools, prompts, and resources. -- Use verbs for tool names, e.g., `get-annotated-message` instead of `annotated-message` - -## Extending the Server - -The Everything Server is designed to be extended at well-defined points. -See [Extension Points](docs/extension.md) and [Project Structure](docs/structure.md). -The server factory is `src/everything/server/index.ts` and registers all features during startup as well as handling post-connection setup. - -### High-level - -- Tools live under `src/everything/tools/` and are registered via `registerTools(server)`. -- Resources live under `src/everything/resources/` and are registered via `registerResources(server)`. -- Prompts live under `src/everything/prompts/` and are registered via `registerPrompts(server)`. -- Subscriptions and simulated update routines are under `src/everything/resources/subscriptions.ts`. -- Logging helpers are under `src/everything/server/logging.ts`. -- Transport managers are under `src/everything/transports/`. - -### When adding a new feature - -- Follow the existing file/module pattern in its folder (naming, exports, and registration function). -- Export a `registerX(server)` function that registers new items with the MCP SDK in the same style as existing ones. -- Wire your new module into the central index (e.g., update `tools/index.ts`, `resources/index.ts`, or `prompts/index.ts`). -- Ensure schemas (for tools) are accurate JSON Schema and include helpful descriptions and examples. - `server/index.ts` and usages in `logging.ts` and `subscriptions.ts`. -- Keep the docs in `src/everything/docs/` up to date if you add or modify noteworthy features. diff --git a/src/everything/Dockerfile b/src/everything/Dockerfile deleted file mode 100644 index 6729298..0000000 --- a/src/everything/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM node:22.12-alpine AS builder - -COPY src/everything /app -COPY tsconfig.json /tsconfig.json - -WORKDIR /app - -RUN --mount=type=cache,target=/root/.npm npm install - -FROM node:22-alpine AS release - -WORKDIR /app - -COPY --from=builder /app/dist /app/dist -COPY --from=builder /app/package.json /app/package.json -COPY --from=builder /app/package-lock.json /app/package-lock.json - -ENV NODE_ENV=production - -RUN npm ci --ignore-scripts --omit-dev - -CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/src/everything/README.md b/src/everything/README.md index f061e94..b00a663 100644 --- a/src/everything/README.md +++ b/src/everything/README.md @@ -1,19 +1,78 @@ -# Everything MCP Server -**[Architecture](docs/architecture.md) -| [Project Structure](docs/structure.md) -| [Startup Process](docs/startup.md) -| [Server Features](docs/features.md) -| [Extension Points](docs/extension.md) -| [How It Works](docs/how-it-works.md)** - +# Everything MCP Server This MCP server attempts to exercise all the features of the MCP protocol. It is not intended to be a useful server, but rather a test server for builders of MCP clients. It implements prompts, tools, resources, sampling, and more to showcase MCP capabilities. -## Tools, Resources, Prompts, and Other Features +## Components -A complete list of the registered MCP primitives and other protocol features demonstrated can be found in the [Server Features](docs/features.md) document. +### Tools -## Usage with Claude Desktop (uses [stdio Transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio)) +1. `echo` + - Simple tool to echo back input messages + - Input: + - `message` (string): Message to echo back + - Returns: Text content with echoed message + +2. `add` + - Adds two numbers together + - Inputs: + - `a` (number): First number + - `b` (number): Second number + - Returns: Text result of the addition + +3. `longRunningOperation` + - Demonstrates progress notifications for long operations + - Inputs: + - `duration` (number, default: 10): Duration in seconds + - `steps` (number, default: 5): Number of progress steps + - Returns: Completion message with duration and steps + - Sends progress notifications during execution + +4. `sampleLLM` + - Demonstrates LLM sampling capability using MCP sampling feature + - Inputs: + - `prompt` (string): The prompt to send to the LLM + - `maxTokens` (number, default: 100): Maximum tokens to generate + - Returns: Generated LLM response + +5. `getTinyImage` + - Returns a small test image + - No inputs required + - Returns: Base64 encoded PNG image data + +### Resources + +The server provides 100 test resources in two formats: +- Even numbered resources: + - Plaintext format + - URI pattern: `test://static/resource/{even_number}` + - Content: Simple text description + +- Odd numbered resources: + - Binary blob format + - URI pattern: `test://static/resource/{odd_number}` + - Content: Base64 encoded binary data + +Resource features: +- Supports pagination (10 items per page) +- Allows subscribing to resource updates +- Demonstrates resource templates +- Auto-updates subscribed resources every 5 seconds + +### Prompts + +1. `simple_prompt` + - Basic prompt without arguments + - Returns: Single message exchange + +2. `complex_prompt` + - Advanced prompt demonstrating argument handling + - Required arguments: + - `temperature` (number): Temperature setting + - Optional arguments: + - `style` (string): Output style preference + - Returns: Multi-turn conversation with images + +## Usage with Claude Desktop Add to your `claude_desktop_config.json`: @@ -30,111 +89,3 @@ Add to your `claude_desktop_config.json`: } } ``` - -On Windows, use `cmd /c` to launch `npx`: - -```json -{ - "mcpServers": { - "everything": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-everything" - ] - } - } -} -``` - -## Usage with VS Code - -For quick installation, use one of the one-click install buttons below... - -[![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=everything&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-everything%22%5D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=everything&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-everything%22%5D%7D&quality=insiders) - -[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=everything&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22mcp%2Feverything%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=everything&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22mcp%2Feverything%22%5D%7D&quality=insiders) - -For manual installation, you can configure the MCP server using one of these methods: - -**Method 1: User Configuration (Recommended)** -Add the configuration to your user-level MCP configuration file. Open the Command Palette (`Ctrl + Shift + P`) and run `MCP: Open User Configuration`. This will open your user `mcp.json` file where you can add the server configuration. - -**Method 2: Workspace Configuration** -Alternatively, you can add the configuration to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. - -> For more details about MCP configuration in VS Code, see the [official VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers). - -#### NPX - -```json -{ - "servers": { - "everything": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-everything"] - } - } -} -``` - -On Windows, use: - -```json -{ - "servers": { - "everything": { - "command": "cmd", - "args": ["/c", "npx", "-y", "@modelcontextprotocol/server-everything"] - } - } -} -``` - -## Running from source with [HTTP+SSE Transport](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) (deprecated as of [2025-03-26](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports)) - -```shell -cd src/everything -npm install -npm run start:sse -``` - -## Run from source with [Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) - -```shell -cd src/everything -npm install -npm run start:streamableHttp -``` - -## Running as an installed package -### Install -```shell -npm install -g @modelcontextprotocol/server-everything@latest -``` - -### Run the default (stdio) server -```shell -npx @modelcontextprotocol/server-everything -``` - -### Or specify stdio explicitly -```shell -npx @modelcontextprotocol/server-everything stdio -``` - -### Run the SSE server -```shell -npx @modelcontextprotocol/server-everything sse -``` - -### Run the streamable HTTP server -```shell -npx @modelcontextprotocol/server-everything streamableHttp -``` - -## License - -This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/everything/__tests__/prompts.test.ts b/src/everything/__tests__/prompts.test.ts deleted file mode 100644 index bff176a..0000000 --- a/src/everything/__tests__/prompts.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { registerSimplePrompt } from '../prompts/simple.js'; -import { registerArgumentsPrompt } from '../prompts/args.js'; -import { registerPromptWithCompletions } from '../prompts/completions.js'; -import { registerEmbeddedResourcePrompt } from '../prompts/resource.js'; - -// Helper to capture registered prompt handlers -function createMockServer() { - const handlers: Map = new Map(); - const configs: Map = new Map(); - - const mockServer = { - registerPrompt: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - configs.set(name, config); - }), - } as unknown as McpServer; - - return { mockServer, handlers, configs }; -} - -describe('Prompts', () => { - describe('simple-prompt', () => { - it('should return fixed message with no arguments', () => { - const { mockServer, handlers } = createMockServer(); - registerSimplePrompt(mockServer); - - const handler = handlers.get('simple-prompt')!; - const result = handler(); - - expect(result).toEqual({ - messages: [ - { - role: 'user', - content: { - type: 'text', - text: 'This is a simple prompt without arguments.', - }, - }, - ], - }); - }); - }); - - describe('args-prompt', () => { - it('should include city in message', () => { - const { mockServer, handlers } = createMockServer(); - registerArgumentsPrompt(mockServer); - - const handler = handlers.get('args-prompt')!; - const result = handler({ city: 'San Francisco' }); - - expect(result.messages[0].content.text).toBe("What's weather in San Francisco?"); - }); - - it('should include city and state in message', () => { - const { mockServer, handlers } = createMockServer(); - registerArgumentsPrompt(mockServer); - - const handler = handlers.get('args-prompt')!; - const result = handler({ city: 'San Francisco', state: 'California' }); - - expect(result.messages[0].content.text).toBe( - "What's weather in San Francisco, California?" - ); - }); - - it('should handle city only (optional state omitted)', () => { - const { mockServer, handlers } = createMockServer(); - registerArgumentsPrompt(mockServer); - - const handler = handlers.get('args-prompt')!; - const result = handler({ city: 'New York' }); - - expect(result.messages[0].content.text).toBe("What's weather in New York?"); - expect(result.messages[0].content.text).not.toContain(','); - expect(result.messages[0].role).toBe('user'); - expect(result.messages[0].content.type).toBe('text'); - }); - }); - - describe('completable-prompt', () => { - it('should generate promotion message with department and name', () => { - const { mockServer, handlers } = createMockServer(); - registerPromptWithCompletions(mockServer); - - const handler = handlers.get('completable-prompt')!; - const result = handler({ department: 'Engineering', name: 'Alice' }); - - expect(result.messages[0].content.text).toBe( - 'Please promote Alice to the head of the Engineering team.' - ); - }); - - it('should work with different departments', () => { - const { mockServer, handlers } = createMockServer(); - registerPromptWithCompletions(mockServer); - - const handler = handlers.get('completable-prompt')!; - - const salesResult = handler({ department: 'Sales', name: 'David' }); - expect(salesResult.messages[0].content.text).toContain('Sales'); - expect(salesResult.messages[0].content.text).toContain('David'); - expect(salesResult.messages[0].role).toBe('user'); - - const marketingResult = handler({ department: 'Marketing', name: 'Grace' }); - expect(marketingResult.messages[0].content.text).toContain('Marketing'); - expect(marketingResult.messages[0].content.text).toContain('Grace'); - }); - }); - - describe('resource-prompt', () => { - it('should return text resource reference', () => { - const { mockServer, handlers } = createMockServer(); - registerEmbeddedResourcePrompt(mockServer); - - const handler = handlers.get('resource-prompt')!; - const result = handler({ resourceType: 'Text', resourceId: '1' }); - - expect(result.messages).toHaveLength(2); - expect(result.messages[0].content.text).toContain('Text'); - expect(result.messages[0].content.text).toContain('1'); - expect(result.messages[1].content.type).toBe('resource'); - expect(result.messages[1].content.resource.uri).toContain('text/1'); - }); - - it('should return blob resource reference', () => { - const { mockServer, handlers } = createMockServer(); - registerEmbeddedResourcePrompt(mockServer); - - const handler = handlers.get('resource-prompt')!; - const result = handler({ resourceType: 'Blob', resourceId: '5' }); - - expect(result.messages[0].content.text).toContain('Blob'); - expect(result.messages[1].content.resource.uri).toContain('blob/5'); - }); - - it('should reject invalid resource type', () => { - const { mockServer, handlers } = createMockServer(); - registerEmbeddedResourcePrompt(mockServer); - - const handler = handlers.get('resource-prompt')!; - expect(() => handler({ resourceType: 'Invalid', resourceId: '1' })).toThrow( - 'Invalid resourceType' - ); - }); - - it('should reject invalid resource ID', () => { - const { mockServer, handlers } = createMockServer(); - registerEmbeddedResourcePrompt(mockServer); - - const handler = handlers.get('resource-prompt')!; - expect(() => handler({ resourceType: 'Text', resourceId: '-1' })).toThrow( - 'Invalid resourceId' - ); - expect(() => handler({ resourceType: 'Text', resourceId: '0' })).toThrow( - 'Invalid resourceId' - ); - expect(() => handler({ resourceType: 'Text', resourceId: 'abc' })).toThrow( - 'Invalid resourceId' - ); - }); - - it('should include both intro text and resource messages', () => { - const { mockServer, handlers } = createMockServer(); - registerEmbeddedResourcePrompt(mockServer); - - const handler = handlers.get('resource-prompt')!; - const result = handler({ resourceType: 'Text', resourceId: '3' }); - - expect(result.messages).toHaveLength(2); - expect(result.messages[0].role).toBe('user'); - expect(result.messages[0].content.type).toBe('text'); - expect(result.messages[1].role).toBe('user'); - expect(result.messages[1].content.type).toBe('resource'); - }); - }); -}); diff --git a/src/everything/__tests__/registrations.test.ts b/src/everything/__tests__/registrations.test.ts deleted file mode 100644 index 421d759..0000000 --- a/src/everything/__tests__/registrations.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; - -// Create mock server -function createMockServer() { - return { - registerTool: vi.fn(), - registerPrompt: vi.fn(), - registerResource: vi.fn(), - server: { - getClientCapabilities: vi.fn(() => ({})), - setRequestHandler: vi.fn(), - }, - sendLoggingMessage: vi.fn(), - sendResourceUpdated: vi.fn(), - } as unknown as McpServer; -} - -describe('Registration Index Files', () => { - describe('tools/index.ts', () => { - it('should register all standard tools', async () => { - const { registerTools } = await import('../tools/index.js'); - const mockServer = createMockServer(); - - registerTools(mockServer); - - // Should register 12 standard tools (non-conditional) - expect(mockServer.registerTool).toHaveBeenCalledTimes(12); - - // Verify specific tools are registered - const registeredTools = (mockServer.registerTool as any).mock.calls.map( - (call: any[]) => call[0] - ); - expect(registeredTools).toContain('echo'); - expect(registeredTools).toContain('get-sum'); - expect(registeredTools).toContain('get-env'); - expect(registeredTools).toContain('get-tiny-image'); - expect(registeredTools).toContain('get-structured-content'); - expect(registeredTools).toContain('get-annotated-message'); - expect(registeredTools).toContain('trigger-long-running-operation'); - expect(registeredTools).toContain('get-resource-links'); - expect(registeredTools).toContain('get-resource-reference'); - expect(registeredTools).toContain('gzip-file-as-resource'); - expect(registeredTools).toContain('toggle-simulated-logging'); - expect(registeredTools).toContain('toggle-subscriber-updates'); - }); - - it('should register conditional tools based on capabilities', async () => { - const { registerConditionalTools } = await import('../tools/index.js'); - - // Server with all capabilities including experimental tasks API - const mockServerWithCapabilities = { - registerTool: vi.fn(), - server: { - getClientCapabilities: vi.fn(() => ({ - roots: {}, - elicitation: { url: {} }, - sampling: {}, - })), - }, - experimental: { - tasks: { - registerToolTask: vi.fn(), - }, - }, - } as unknown as McpServer; - - registerConditionalTools(mockServerWithCapabilities); - - // Should register 4 conditional tools via registerTool when all capabilities - // are present. Task-based tools register via registerToolTask (counted separately), - // so they are not included in this registerTool count. - expect(mockServerWithCapabilities.registerTool).toHaveBeenCalledTimes(4); - - const registeredTools = ( - mockServerWithCapabilities.registerTool as any - ).mock.calls.map((call: any[]) => call[0]); - expect(registeredTools).toContain('get-roots-list'); - expect(registeredTools).toContain('trigger-elicitation-request'); - expect(registeredTools).toContain('trigger-url-elicitation'); - expect(registeredTools).toContain('trigger-sampling-request'); - - // Task-based tools are registered via experimental.tasks.registerToolTask - expect(mockServerWithCapabilities.experimental.tasks.registerToolTask).toHaveBeenCalled(); - }); - - it('should not register conditional tools when capabilities missing', async () => { - const { registerConditionalTools } = await import('../tools/index.js'); - - const mockServerNoCapabilities = { - registerTool: vi.fn(), - server: { - getClientCapabilities: vi.fn(() => ({})), - }, - experimental: { - tasks: { - registerToolTask: vi.fn(), - }, - }, - } as unknown as McpServer; - - registerConditionalTools(mockServerNoCapabilities); - - // Should not register any capability-gated tools when capabilities are missing - expect(mockServerNoCapabilities.registerTool).not.toHaveBeenCalled(); - }); - }); - - describe('prompts/index.ts', () => { - it('should register all prompts', async () => { - const { registerPrompts } = await import('../prompts/index.js'); - const mockServer = createMockServer(); - - registerPrompts(mockServer); - - // Should register 4 prompts - expect(mockServer.registerPrompt).toHaveBeenCalledTimes(4); - - const registeredPrompts = (mockServer.registerPrompt as any).mock.calls.map( - (call: any[]) => call[0] - ); - expect(registeredPrompts).toContain('simple-prompt'); - expect(registeredPrompts).toContain('args-prompt'); - expect(registeredPrompts).toContain('completable-prompt'); - expect(registeredPrompts).toContain('resource-prompt'); - }); - }); - - describe('resources/index.ts', () => { - it('should register resource templates', async () => { - const { registerResources } = await import('../resources/index.js'); - const mockServer = createMockServer(); - - registerResources(mockServer); - - // Should register at least the 2 resource templates (text and blob) plus file resources - expect(mockServer.registerResource).toHaveBeenCalled(); - const registeredResources = (mockServer.registerResource as any).mock.calls.map( - (call: any[]) => call[0] - ); - expect(registeredResources).toContain('Dynamic Text Resource'); - expect(registeredResources).toContain('Dynamic Blob Resource'); - }); - - it('should read instructions from file', async () => { - const { readInstructions } = await import('../resources/index.js'); - - const instructions = readInstructions(); - - // Should return a string (either content or error message) - expect(typeof instructions).toBe('string'); - expect(instructions.length).toBeGreaterThan(0); - }); - }); -}); diff --git a/src/everything/__tests__/resources.test.ts b/src/everything/__tests__/resources.test.ts deleted file mode 100644 index a22b904..0000000 --- a/src/everything/__tests__/resources.test.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { - textResource, - blobResource, - textResourceUri, - blobResourceUri, - RESOURCE_TYPE_TEXT, - RESOURCE_TYPE_BLOB, - RESOURCE_TYPES, - resourceTypeCompleter, - resourceIdForPromptCompleter, - resourceIdForResourceTemplateCompleter, - registerResourceTemplates, -} from '../resources/templates.js'; -import { - getSessionResourceURI, - registerSessionResource, -} from '../resources/session.js'; -import { registerFileResources } from '../resources/files.js'; -import { - setSubscriptionHandlers, - beginSimulatedResourceUpdates, - stopSimulatedResourceUpdates, -} from '../resources/subscriptions.js'; - -describe('Resource Templates', () => { - describe('Constants', () => { - it('should include both types in RESOURCE_TYPES array', () => { - expect(RESOURCE_TYPES).toContain(RESOURCE_TYPE_TEXT); - expect(RESOURCE_TYPES).toContain(RESOURCE_TYPE_BLOB); - expect(RESOURCE_TYPES).toHaveLength(2); - }); - }); - - describe('textResourceUri', () => { - it('should create URL for text resource', () => { - const uri = textResourceUri(1); - expect(uri.toString()).toBe('demo://resource/dynamic/text/1'); - }); - - it('should handle different resource IDs', () => { - expect(textResourceUri(5).toString()).toBe('demo://resource/dynamic/text/5'); - expect(textResourceUri(100).toString()).toBe('demo://resource/dynamic/text/100'); - }); - }); - - describe('blobResourceUri', () => { - it('should create URL for blob resource', () => { - const uri = blobResourceUri(1); - expect(uri.toString()).toBe('demo://resource/dynamic/blob/1'); - }); - - it('should handle different resource IDs', () => { - expect(blobResourceUri(5).toString()).toBe('demo://resource/dynamic/blob/5'); - expect(blobResourceUri(100).toString()).toBe('demo://resource/dynamic/blob/100'); - }); - }); - - describe('textResource', () => { - it('should create text resource with correct structure', () => { - const uri = textResourceUri(1); - const resource = textResource(uri, 1); - - expect(resource.uri).toBe(uri.toString()); - expect(resource.mimeType).toBe('text/plain'); - expect(resource.text).toContain('Resource 1'); - expect(resource.text).toContain('plaintext'); - }); - - it('should include timestamp in content', () => { - const uri = textResourceUri(2); - const resource = textResource(uri, 2); - - // Timestamp format varies, just check it contains time-related content - expect(resource.text).toMatch(/\d/); - }); - }); - - describe('blobResource', () => { - it('should create blob resource with correct structure', () => { - const uri = blobResourceUri(1); - const resource = blobResource(uri, 1); - - expect(resource.uri).toBe(uri.toString()); - expect(resource.mimeType).toBe('text/plain'); - expect(resource.blob).toBeDefined(); - }); - - it('should create valid base64 encoded content', () => { - const uri = blobResourceUri(3); - const resource = blobResource(uri, 3); - - // Decode and verify content - const decoded = Buffer.from(resource.blob, 'base64').toString(); - expect(decoded).toContain('Resource 3'); - expect(decoded).toContain('base64 blob'); - }); - }); - - describe('resourceTypeCompleter', () => { - it('should be defined as a completable schema', () => { - // The completer is a zod schema wrapped with completable - expect(resourceTypeCompleter).toBeDefined(); - // It should have the zod parse method - expect(typeof (resourceTypeCompleter as any).parse).toBe('function'); - }); - - it('should validate string resource types', () => { - // Test that valid strings pass validation - expect(() => (resourceTypeCompleter as any).parse('Text')).not.toThrow(); - expect(() => (resourceTypeCompleter as any).parse('Blob')).not.toThrow(); - }); - }); - - describe('resourceIdForPromptCompleter', () => { - it('should be defined as a completable schema', () => { - expect(resourceIdForPromptCompleter).toBeDefined(); - expect(typeof (resourceIdForPromptCompleter as any).parse).toBe('function'); - }); - - it('should validate string IDs', () => { - // Test that valid strings pass validation - expect(() => (resourceIdForPromptCompleter as any).parse('1')).not.toThrow(); - expect(() => (resourceIdForPromptCompleter as any).parse('100')).not.toThrow(); - }); - }); - - describe('resourceIdForResourceTemplateCompleter', () => { - it('should validate positive integer IDs', () => { - expect(resourceIdForResourceTemplateCompleter('1')).toEqual(['1']); - expect(resourceIdForResourceTemplateCompleter('50')).toEqual(['50']); - }); - - it('should reject invalid IDs', () => { - expect(resourceIdForResourceTemplateCompleter('0')).toEqual([]); - expect(resourceIdForResourceTemplateCompleter('-5')).toEqual([]); - expect(resourceIdForResourceTemplateCompleter('not-a-number')).toEqual([]); - }); - }); - - describe('registerResourceTemplates', () => { - it('should register text and blob resource templates', () => { - const registeredResources: any[] = []; - - const mockServer = { - registerResource: vi.fn((...args) => { - registeredResources.push(args); - }), - } as unknown as McpServer; - - registerResourceTemplates(mockServer); - - expect(mockServer.registerResource).toHaveBeenCalledTimes(2); - - // Check text resource registration - const textRegistration = registeredResources.find((r) => - r[0].includes('Text') - ); - expect(textRegistration).toBeDefined(); - expect(textRegistration[1]).toBeInstanceOf(ResourceTemplate); - - // Check blob resource registration - const blobRegistration = registeredResources.find((r) => - r[0].includes('Blob') - ); - expect(blobRegistration).toBeDefined(); - }); - }); -}); - -describe('Session Resources', () => { - describe('getSessionResourceURI', () => { - it('should generate correct URI for resource name', () => { - expect(getSessionResourceURI('test')).toBe('demo://resource/session/test'); - }); - - it('should handle various resource names', () => { - expect(getSessionResourceURI('my-file')).toBe('demo://resource/session/my-file'); - expect(getSessionResourceURI('document_123')).toBe( - 'demo://resource/session/document_123' - ); - }); - }); - - describe('registerSessionResource', () => { - it('should register text resource and return resource link', () => { - const registrations: any[] = []; - const mockServer = { - registerResource: vi.fn((...args) => { - registrations.push(args); - }), - } as unknown as McpServer; - - const resource = { - uri: 'demo://resource/session/test-file', - name: 'test-file', - mimeType: 'text/plain', - description: 'A test file', - }; - - const result = registerSessionResource( - mockServer, - resource, - 'text', - 'Hello, World!' - ); - - expect(result.type).toBe('resource_link'); - expect(result.uri).toBe(resource.uri); - expect(result.name).toBe(resource.name); - - expect(mockServer.registerResource).toHaveBeenCalledWith( - 'test-file', - 'demo://resource/session/test-file', - expect.objectContaining({ - mimeType: 'text/plain', - description: 'A test file', - }), - expect.any(Function) - ); - }); - - it('should register blob resource correctly', () => { - const mockServer = { - registerResource: vi.fn(), - } as unknown as McpServer; - - const resource = { - uri: 'demo://resource/session/binary-file', - name: 'binary-file', - mimeType: 'application/octet-stream', - }; - - const blobContent = Buffer.from('binary data').toString('base64'); - const result = registerSessionResource(mockServer, resource, 'blob', blobContent); - - expect(result.type).toBe('resource_link'); - expect(mockServer.registerResource).toHaveBeenCalled(); - }); - - it('should return resource handler that provides correct content', async () => { - let capturedHandler: Function | null = null; - const mockServer = { - registerResource: vi.fn((_name, _uri, _config, handler) => { - capturedHandler = handler; - }), - } as unknown as McpServer; - - const resource = { - uri: 'demo://resource/session/content-test', - name: 'content-test', - mimeType: 'text/plain', - }; - - registerSessionResource(mockServer, resource, 'text', 'Test content here'); - - expect(capturedHandler).not.toBeNull(); - - const handlerResult = await capturedHandler!(new URL(resource.uri)); - expect(handlerResult.contents).toHaveLength(1); - expect(handlerResult.contents[0].text).toBe('Test content here'); - expect(handlerResult.contents[0].mimeType).toBe('text/plain'); - }); - }); -}); - -describe('File Resources', () => { - describe('registerFileResources', () => { - it('should register file resources when docs directory exists', () => { - const mockServer = { - registerResource: vi.fn(), - } as unknown as McpServer; - - registerFileResources(mockServer); - - // The docs folder exists in the everything server and contains files - // so registerResource should have been called - expect(mockServer.registerResource).toHaveBeenCalled(); - }); - }); -}); - -describe('Subscriptions', () => { - describe('setSubscriptionHandlers', () => { - it('should set request handlers on server', () => { - const mockServer = { - server: { - setRequestHandler: vi.fn(), - }, - sendLoggingMessage: vi.fn(), - } as unknown as McpServer; - - setSubscriptionHandlers(mockServer); - - // Should set both subscribe and unsubscribe handlers - expect(mockServer.server.setRequestHandler).toHaveBeenCalledTimes(2); - }); - }); - - describe('simulated resource updates lifecycle', () => { - afterEach(() => { - // Clean up any intervals - stopSimulatedResourceUpdates('lifecycle-test-session'); - }); - - it('should start and stop updates without errors', () => { - const mockServer = { - server: { - notification: vi.fn(), - }, - } as unknown as McpServer; - - // Start updates - should work for both defined and undefined sessionId - beginSimulatedResourceUpdates(mockServer, 'lifecycle-test-session'); - beginSimulatedResourceUpdates(mockServer, undefined); - - // Stop updates - should handle all cases gracefully - stopSimulatedResourceUpdates('lifecycle-test-session'); - stopSimulatedResourceUpdates('non-existent-session'); - stopSimulatedResourceUpdates(undefined); - - // If we got here without throwing, the lifecycle works correctly - expect(true).toBe(true); - }); - }); -}); diff --git a/src/everything/__tests__/server.test.ts b/src/everything/__tests__/server.test.ts deleted file mode 100644 index e7985dd..0000000 --- a/src/everything/__tests__/server.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { createServer } from '../server/index.js'; - -describe('Server Factory', () => { - describe('createServer', () => { - it('should return a ServerFactoryResponse object', () => { - const result = createServer(); - - expect(result).toHaveProperty('server'); - expect(result).toHaveProperty('cleanup'); - }); - - it('should return a cleanup function', () => { - const { cleanup } = createServer(); - - expect(typeof cleanup).toBe('function'); - }); - - it('should create an McpServer instance', () => { - const { server } = createServer(); - - expect(server).toBeDefined(); - expect(server.server).toBeDefined(); - }); - - it('should have an oninitialized handler set', () => { - const { server } = createServer(); - - expect(server.server.oninitialized).toBeDefined(); - }); - - it('should allow multiple servers to be created', () => { - const result1 = createServer(); - const result2 = createServer(); - - expect(result1.server).toBeDefined(); - expect(result2.server).toBeDefined(); - expect(result1.server).not.toBe(result2.server); - }); - }); -}); diff --git a/src/everything/__tests__/tools.test.ts b/src/everything/__tests__/tools.test.ts deleted file mode 100644 index a50bbd6..0000000 --- a/src/everything/__tests__/tools.test.ts +++ /dev/null @@ -1,1221 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { registerEchoTool, EchoSchema } from '../tools/echo.js'; -import { registerGetSumTool } from '../tools/get-sum.js'; -import { registerGetEnvTool } from '../tools/get-env.js'; -import { registerGetTinyImageTool, MCP_TINY_IMAGE } from '../tools/get-tiny-image.js'; -import { registerGetStructuredContentTool } from '../tools/get-structured-content.js'; -import { registerGetAnnotatedMessageTool } from '../tools/get-annotated-message.js'; -import { registerTriggerLongRunningOperationTool } from '../tools/trigger-long-running-operation.js'; -import { registerGetResourceLinksTool } from '../tools/get-resource-links.js'; -import { registerGetResourceReferenceTool } from '../tools/get-resource-reference.js'; -import { registerToggleSimulatedLoggingTool } from '../tools/toggle-simulated-logging.js'; -import { registerToggleSubscriberUpdatesTool } from '../tools/toggle-subscriber-updates.js'; -import { registerTriggerSamplingRequestTool } from '../tools/trigger-sampling-request.js'; -import { registerTriggerElicitationRequestTool } from '../tools/trigger-elicitation-request.js'; -import { - registerTriggerUrlElicitationTool, - __resetIssuedErrorPathElicitations, -} from '../tools/trigger-url-elicitation.js'; -import { registerGetRootsListTool } from '../tools/get-roots-list.js'; -import { registerGZipFileAsResourceTool } from '../tools/gzip-file-as-resource.js'; -import { registerSimulateResearchQueryTool } from '../tools/simulate-research-query.js'; - -// Helper to capture registered tool handlers -function createMockServer() { - const handlers: Map = new Map(); - const configs: Map = new Map(); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - configs.set(name, config); - }), - server: { - getClientCapabilities: vi.fn(() => ({})), - notification: vi.fn(), - }, - sendLoggingMessage: vi.fn(), - sendResourceUpdated: vi.fn(), - } as unknown as McpServer; - - return { mockServer, handlers, configs }; -} - -describe('Tools', () => { - describe('echo', () => { - it('should echo back the message', async () => { - const { mockServer, handlers } = createMockServer(); - registerEchoTool(mockServer); - - const handler = handlers.get('echo')!; - const result = await handler({ message: 'Hello, World!' }); - - expect(result).toEqual({ - content: [{ type: 'text', text: 'Echo: Hello, World!' }], - }); - }); - - it('should handle empty message', async () => { - const { mockServer, handlers } = createMockServer(); - registerEchoTool(mockServer); - - const handler = handlers.get('echo')!; - const result = await handler({ message: '' }); - - expect(result).toEqual({ - content: [{ type: 'text', text: 'Echo: ' }], - }); - }); - - it('should reject invalid input', async () => { - const { mockServer, handlers } = createMockServer(); - registerEchoTool(mockServer); - - const handler = handlers.get('echo')!; - - await expect(handler({})).rejects.toThrow(); - await expect(handler({ message: 123 })).rejects.toThrow(); - }); - }); - - describe('EchoSchema', () => { - it('should validate correct input', () => { - const result = EchoSchema.parse({ message: 'test' }); - expect(result).toEqual({ message: 'test' }); - }); - - it('should reject missing message', () => { - expect(() => EchoSchema.parse({})).toThrow(); - }); - - it('should reject non-string message', () => { - expect(() => EchoSchema.parse({ message: 123 })).toThrow(); - }); - }); - - describe('get-sum', () => { - it('should calculate sum of two positive numbers', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetSumTool(mockServer); - - const handler = handlers.get('get-sum')!; - const result = await handler({ a: 5, b: 3 }); - - expect(result).toEqual({ - content: [{ type: 'text', text: 'The sum of 5 and 3 is 8.' }], - }); - }); - - it('should calculate sum with negative numbers', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetSumTool(mockServer); - - const handler = handlers.get('get-sum')!; - const result = await handler({ a: -5, b: 3 }); - - expect(result).toEqual({ - content: [{ type: 'text', text: 'The sum of -5 and 3 is -2.' }], - }); - }); - - it('should calculate sum with zero', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetSumTool(mockServer); - - const handler = handlers.get('get-sum')!; - const result = await handler({ a: 0, b: 0 }); - - expect(result).toEqual({ - content: [{ type: 'text', text: 'The sum of 0 and 0 is 0.' }], - }); - }); - - it('should handle floating point numbers', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetSumTool(mockServer); - - const handler = handlers.get('get-sum')!; - const result = await handler({ a: 1.5, b: 2.5 }); - - expect(result).toEqual({ - content: [{ type: 'text', text: 'The sum of 1.5 and 2.5 is 4.' }], - }); - }); - - it('should reject invalid input', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetSumTool(mockServer); - - const handler = handlers.get('get-sum')!; - - await expect(handler({})).rejects.toThrow(); - await expect(handler({ a: 'not a number', b: 5 })).rejects.toThrow(); - await expect(handler({ a: 5 })).rejects.toThrow(); - }); - }); - - describe('get-env', () => { - it('should return all environment variables as JSON', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetEnvTool(mockServer); - - const handler = handlers.get('get-env')!; - process.env.TEST_VAR_EVERYTHING = 'test_value'; - const result = await handler({}); - - expect(result.content).toHaveLength(1); - expect(result.content[0].type).toBe('text'); - - const envJson = JSON.parse(result.content[0].text); - expect(envJson.TEST_VAR_EVERYTHING).toBe('test_value'); - - delete process.env.TEST_VAR_EVERYTHING; - }); - - it('should return valid JSON', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetEnvTool(mockServer); - - const handler = handlers.get('get-env')!; - const result = await handler({}); - - expect(() => JSON.parse(result.content[0].text)).not.toThrow(); - }); - }); - - describe('get-tiny-image', () => { - it('should return image content with text descriptions', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetTinyImageTool(mockServer); - - const handler = handlers.get('get-tiny-image')!; - const result = await handler({}); - - expect(result.content).toHaveLength(3); - expect(result.content[0]).toEqual({ - type: 'text', - text: "Here's the image you requested:", - }); - expect(result.content[1]).toEqual({ - type: 'image', - data: MCP_TINY_IMAGE, - mimeType: 'image/png', - }); - expect(result.content[2]).toEqual({ - type: 'text', - text: 'The image above is the MCP logo.', - }); - }); - - it('should return valid base64 image data', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetTinyImageTool(mockServer); - - const handler = handlers.get('get-tiny-image')!; - const result = await handler({}); - - const imageContent = result.content[1]; - expect(imageContent.type).toBe('image'); - expect(imageContent.mimeType).toBe('image/png'); - // Verify it's valid base64 - expect(() => Buffer.from(imageContent.data, 'base64')).not.toThrow(); - }); - }); - - describe('get-structured-content', () => { - it('should return weather for New York', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetStructuredContentTool(mockServer); - - const handler = handlers.get('get-structured-content')!; - const result = await handler({ location: 'New York' }); - - expect(result.structuredContent).toEqual({ - temperature: 33, - conditions: 'Cloudy', - humidity: 82, - }); - expect(result.content[0].type).toBe('text'); - expect(JSON.parse(result.content[0].text)).toEqual(result.structuredContent); - }); - - it('should return weather for Chicago', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetStructuredContentTool(mockServer); - - const handler = handlers.get('get-structured-content')!; - const result = await handler({ location: 'Chicago' }); - - expect(result.structuredContent).toEqual({ - temperature: 36, - conditions: 'Light rain / drizzle', - humidity: 82, - }); - }); - - it('should return weather for Los Angeles', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetStructuredContentTool(mockServer); - - const handler = handlers.get('get-structured-content')!; - const result = await handler({ location: 'Los Angeles' }); - - expect(result.structuredContent).toEqual({ - temperature: 73, - conditions: 'Sunny / Clear', - humidity: 48, - }); - }); - }); - - describe('get-annotated-message', () => { - it('should return error message with high priority', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetAnnotatedMessageTool(mockServer); - - const handler = handlers.get('get-annotated-message')!; - const result = await handler({ messageType: 'error', includeImage: false }); - - expect(result.content).toHaveLength(1); - expect(result.content[0].text).toBe('Error: Operation failed'); - expect(result.content[0].annotations).toEqual({ - priority: 1.0, - audience: ['user', 'assistant'], - }); - }); - - it('should return success message with medium priority', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetAnnotatedMessageTool(mockServer); - - const handler = handlers.get('get-annotated-message')!; - const result = await handler({ messageType: 'success', includeImage: false }); - - expect(result.content[0].text).toBe('Operation completed successfully'); - expect(result.content[0].annotations.priority).toBe(0.7); - expect(result.content[0].annotations.audience).toEqual(['user']); - }); - - it('should return debug message with low priority', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetAnnotatedMessageTool(mockServer); - - const handler = handlers.get('get-annotated-message')!; - const result = await handler({ messageType: 'debug', includeImage: false }); - - expect(result.content[0].text).toContain('Debug:'); - expect(result.content[0].annotations.priority).toBe(0.3); - expect(result.content[0].annotations.audience).toEqual(['assistant']); - }); - - it('should include annotated image when requested', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetAnnotatedMessageTool(mockServer); - - const handler = handlers.get('get-annotated-message')!; - const result = await handler({ messageType: 'success', includeImage: true }); - - expect(result.content).toHaveLength(2); - expect(result.content[1].type).toBe('image'); - expect(result.content[1].annotations).toEqual({ - priority: 0.5, - audience: ['user'], - }); - }); - }); - - describe('trigger-long-running-operation', () => { - it('should complete operation and return result', async () => { - const { mockServer, handlers } = createMockServer(); - registerTriggerLongRunningOperationTool(mockServer); - - const handler = handlers.get('trigger-long-running-operation')!; - // Use very short duration for test - const result = await handler( - { duration: 0.1, steps: 2 }, - { _meta: {}, requestId: 'test-123' } - ); - - expect(result.content[0].text).toContain('Long running operation completed'); - expect(result.content[0].text).toContain('Duration: 0.1 seconds'); - expect(result.content[0].text).toContain('Steps: 2'); - }, 10000); - - it('should send progress notifications when progressToken provided', async () => { - const { mockServer, handlers } = createMockServer(); - registerTriggerLongRunningOperationTool(mockServer); - - const handler = handlers.get('trigger-long-running-operation')!; - await handler( - { duration: 0.1, steps: 2 }, - { _meta: { progressToken: 'token-123' }, requestId: 'test-456', sessionId: 'session-1' } - ); - - expect(mockServer.server.notification).toHaveBeenCalledTimes(2); - expect(mockServer.server.notification).toHaveBeenCalledWith( - expect.objectContaining({ - method: 'notifications/progress', - params: expect.objectContaining({ - progressToken: 'token-123', - }), - }), - expect.any(Object) - ); - }, 10000); - }); - - describe('get-resource-links', () => { - it('should return specified number of resource links', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetResourceLinksTool(mockServer); - - const handler = handlers.get('get-resource-links')!; - const result = await handler({ count: 3 }); - - // 1 intro text + 3 resource links - expect(result.content).toHaveLength(4); - expect(result.content[0].type).toBe('text'); - expect(result.content[0].text).toContain('3 resource links'); - - // Check resource links - for (let i = 1; i < 4; i++) { - expect(result.content[i].type).toBe('resource_link'); - expect(result.content[i].uri).toBeDefined(); - expect(result.content[i].name).toBeDefined(); - } - }); - - it('should alternate between text and blob resources', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetResourceLinksTool(mockServer); - - const handler = handlers.get('get-resource-links')!; - const result = await handler({ count: 4 }); - - // Odd IDs (1, 3) are blob, even IDs (2, 4) are text - expect(result.content[1].name).toContain('Blob'); - expect(result.content[2].name).toContain('Text'); - expect(result.content[3].name).toContain('Blob'); - expect(result.content[4].name).toContain('Text'); - }); - - it('should use default count of 3', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetResourceLinksTool(mockServer); - - const handler = handlers.get('get-resource-links')!; - const result = await handler({}); - - // 1 intro text + 3 resource links (default) - expect(result.content).toHaveLength(4); - }); - }); - - describe('get-resource-reference', () => { - it('should return text resource reference', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetResourceReferenceTool(mockServer); - - const handler = handlers.get('get-resource-reference')!; - const result = await handler({ resourceType: 'Text', resourceId: 1 }); - - expect(result.content).toHaveLength(3); - expect(result.content[0].text).toContain('Resource 1'); - expect(result.content[1].type).toBe('resource'); - expect(result.content[1].resource.uri).toContain('text/1'); - expect(result.content[2].text).toContain('URI'); - }); - - it('should return blob resource reference', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetResourceReferenceTool(mockServer); - - const handler = handlers.get('get-resource-reference')!; - const result = await handler({ resourceType: 'Blob', resourceId: 5 }); - - expect(result.content[1].resource.uri).toContain('blob/5'); - }); - - it('should reject invalid resource type', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetResourceReferenceTool(mockServer); - - const handler = handlers.get('get-resource-reference')!; - await expect(handler({ resourceType: 'Invalid', resourceId: 1 })).rejects.toThrow( - 'Invalid resourceType' - ); - }); - - it('should reject invalid resource ID', async () => { - const { mockServer, handlers } = createMockServer(); - registerGetResourceReferenceTool(mockServer); - - const handler = handlers.get('get-resource-reference')!; - await expect(handler({ resourceType: 'Text', resourceId: -1 })).rejects.toThrow( - 'Invalid resourceId' - ); - await expect(handler({ resourceType: 'Text', resourceId: 0 })).rejects.toThrow( - 'Invalid resourceId' - ); - await expect(handler({ resourceType: 'Text', resourceId: 1.5 })).rejects.toThrow( - 'Invalid resourceId' - ); - }); - }); - - describe('toggle-simulated-logging', () => { - it('should start logging when not active', async () => { - const { mockServer, handlers } = createMockServer(); - registerToggleSimulatedLoggingTool(mockServer); - - const handler = handlers.get('toggle-simulated-logging')!; - const result = await handler({}, { sessionId: 'test-session-1' }); - - expect(result.content[0].text).toContain('Started'); - expect(result.content[0].text).toContain('test-session-1'); - }); - - it('should stop logging when already active', async () => { - const { mockServer, handlers } = createMockServer(); - registerToggleSimulatedLoggingTool(mockServer); - - const handler = handlers.get('toggle-simulated-logging')!; - - // First call starts logging - await handler({}, { sessionId: 'test-session-2' }); - - // Second call stops logging - const result = await handler({}, { sessionId: 'test-session-2' }); - - expect(result.content[0].text).toContain('Stopped'); - expect(result.content[0].text).toContain('test-session-2'); - }); - - it('should handle undefined sessionId', async () => { - const { mockServer, handlers } = createMockServer(); - registerToggleSimulatedLoggingTool(mockServer); - - const handler = handlers.get('toggle-simulated-logging')!; - const result = await handler({}, {}); - - expect(result.content[0].text).toContain('Started'); - }); - }); - - describe('toggle-subscriber-updates', () => { - it('should start updates when not active', async () => { - const { mockServer, handlers } = createMockServer(); - registerToggleSubscriberUpdatesTool(mockServer); - - const handler = handlers.get('toggle-subscriber-updates')!; - const result = await handler({}, { sessionId: 'sub-session-1' }); - - expect(result.content[0].text).toContain('Started'); - expect(result.content[0].text).toContain('sub-session-1'); - }); - - it('should stop updates when already active', async () => { - const { mockServer, handlers } = createMockServer(); - registerToggleSubscriberUpdatesTool(mockServer); - - const handler = handlers.get('toggle-subscriber-updates')!; - - // First call starts updates - await handler({}, { sessionId: 'sub-session-2' }); - - // Second call stops updates - const result = await handler({}, { sessionId: 'sub-session-2' }); - - expect(result.content[0].text).toContain('Stopped'); - expect(result.content[0].text).toContain('sub-session-2'); - }); - }); - - describe('trigger-sampling-request', () => { - it('should not register when client does not support sampling', () => { - const { mockServer } = createMockServer(); - registerTriggerSamplingRequestTool(mockServer); - - // Tool should not be registered since mock server returns empty capabilities - expect(mockServer.registerTool).not.toHaveBeenCalled(); - }); - - it('should register when client supports sampling', () => { - const handlers: Map = new Map(); - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ sampling: {} })), - }, - } as unknown as McpServer; - - registerTriggerSamplingRequestTool(mockServer); - - expect(mockServer.registerTool).toHaveBeenCalledWith( - 'trigger-sampling-request', - expect.objectContaining({ - title: 'Trigger Sampling Request Tool', - description: expect.stringContaining('Sampling'), - }), - expect.any(Function) - ); - }); - - it('should send sampling request and return result', async () => { - const handlers: Map = new Map(); - const mockSendRequest = vi.fn().mockResolvedValue({ - model: 'test-model', - content: { type: 'text', text: 'LLM response' }, - }); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ sampling: {} })), - }, - } as unknown as McpServer; - - registerTriggerSamplingRequestTool(mockServer); - - const handler = handlers.get('trigger-sampling-request')!; - const result = await handler( - { prompt: 'Test prompt', maxTokens: 50 }, - { sendRequest: mockSendRequest } - ); - - expect(mockSendRequest).toHaveBeenCalledWith( - expect.objectContaining({ - method: 'sampling/createMessage', - params: expect.objectContaining({ - maxTokens: 50, - }), - }), - expect.anything() - ); - expect(result.content[0].text).toContain('LLM sampling result'); - }); - }); - - describe('trigger-elicitation-request', () => { - it('should not register when client does not support elicitation', () => { - const { mockServer } = createMockServer(); - registerTriggerElicitationRequestTool(mockServer); - - expect(mockServer.registerTool).not.toHaveBeenCalled(); - }); - - it('should register when client supports elicitation', () => { - const handlers: Map = new Map(); - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: {} })), - }, - } as unknown as McpServer; - - registerTriggerElicitationRequestTool(mockServer); - - expect(mockServer.registerTool).toHaveBeenCalledWith( - 'trigger-elicitation-request', - expect.objectContaining({ - title: 'Trigger Elicitation Request Tool', - description: expect.stringContaining('Elicitation'), - }), - expect.any(Function) - ); - }); - - it('should handle accept action with user content', async () => { - const handlers: Map = new Map(); - const mockSendRequest = vi.fn().mockResolvedValue({ - action: 'accept', - content: { - name: 'John Doe', - check: true, - email: 'john@example.com', - }, - }); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: {} })), - }, - } as unknown as McpServer; - - registerTriggerElicitationRequestTool(mockServer); - - const handler = handlers.get('trigger-elicitation-request')!; - const result = await handler({}, { sendRequest: mockSendRequest }); - - expect(result.content[0].text).toContain('โœ…'); - expect(result.content[0].text).toContain('provided'); - expect(result.content[1].text).toContain('John Doe'); - }); - - it('should handle decline action', async () => { - const handlers: Map = new Map(); - const mockSendRequest = vi.fn().mockResolvedValue({ - action: 'decline', - }); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: {} })), - }, - } as unknown as McpServer; - - registerTriggerElicitationRequestTool(mockServer); - - const handler = handlers.get('trigger-elicitation-request')!; - const result = await handler({}, { sendRequest: mockSendRequest }); - - expect(result.content[0].text).toContain('โŒ'); - expect(result.content[0].text).toContain('declined'); - }); - - it('should handle cancel action', async () => { - const handlers: Map = new Map(); - const mockSendRequest = vi.fn().mockResolvedValue({ - action: 'cancel', - }); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: {} })), - }, - } as unknown as McpServer; - - registerTriggerElicitationRequestTool(mockServer); - - const handler = handlers.get('trigger-elicitation-request')!; - const result = await handler({}, { sendRequest: mockSendRequest }); - - expect(result.content[0].text).toContain('โš ๏ธ'); - expect(result.content[0].text).toContain('cancelled'); - }); - }); - - describe('trigger-url-elicitation', () => { - // The error-path marker is module-level state shared across cases; reset it - // so tests are independent of order and of each other's leftover keys. - beforeEach(() => { - __resetIssuedErrorPathElicitations(); - }); - - it('should not register when client does not support URL elicitation', () => { - const handlers: Map = new Map(); - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: { form: {} } })), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - expect(mockServer.registerTool).not.toHaveBeenCalled(); - }); - - it('should register when client supports URL elicitation', () => { - const handlers: Map = new Map(); - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - expect(mockServer.registerTool).toHaveBeenCalledWith( - 'trigger-url-elicitation', - expect.objectContaining({ - title: 'Trigger URL Elicitation Tool', - description: expect.stringContaining('URL elicitation'), - }), - expect.any(Function) - ); - }); - - it('should send URL-mode elicitation request when errorPath is false', async () => { - const handlers: Map = new Map(); - const mockSendRequest = vi.fn().mockResolvedValue({ - action: 'accept', - }); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - const handler = handlers.get('trigger-url-elicitation')!; - const result = await handler( - { - url: 'https://example.com/verify', - message: 'Open this page to verify your identity', - elicitationId: 'elicitation-123', - errorPath: false, - }, - { sendRequest: mockSendRequest } - ); - - expect(mockSendRequest).toHaveBeenCalledWith( - expect.objectContaining({ - method: 'elicitation/create', - params: expect.objectContaining({ - mode: 'url', - url: 'https://example.com/verify', - message: 'Open this page to verify your identity', - elicitationId: 'elicitation-123', - }), - }), - expect.anything(), - expect.anything() - ); - - expect(result.content[0].text).toContain( - 'โœ… User completed the URL elicitation flow.' - ); - }); - - it('should not register when client has no elicitation capability at all', () => { - const mockServer = { - registerTool: vi.fn(), - server: { - getClientCapabilities: vi.fn(() => ({})), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - expect(mockServer.registerTool).not.toHaveBeenCalled(); - }); - - it('should not register when client capabilities are undefined', () => { - const mockServer = { - registerTool: vi.fn(), - server: { - getClientCapabilities: vi.fn(() => undefined), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - expect(mockServer.registerTool).not.toHaveBeenCalled(); - }); - - it('should default the elicitationId to a random UUID when omitted', async () => { - const handlers: Map = new Map(); - const mockSendRequest = vi.fn().mockResolvedValue({ - action: 'accept', - }); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - const handler = handlers.get('trigger-url-elicitation')!; - await handler( - { - url: 'https://example.com/verify', - message: 'Open this page to verify your identity', - errorPath: false, - }, - { sendRequest: mockSendRequest } - ); - - const sentParams = mockSendRequest.mock.calls[0][0].params; - expect(sentParams.elicitationId).toMatch( - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ - ); - }); - - it('should report a declined URL elicitation', async () => { - const handlers: Map = new Map(); - const mockSendRequest = vi.fn().mockResolvedValue({ action: 'decline' }); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - const handler = handlers.get('trigger-url-elicitation')!; - const result = await handler( - { - url: 'https://example.com/verify', - message: 'Open this page to verify your identity', - elicitationId: 'elicitation-123', - errorPath: false, - }, - { sendRequest: mockSendRequest } - ); - - expect(result.content[0].text).toContain('โŒ User declined to open the URL'); - }); - - it('should report a cancelled URL elicitation', async () => { - const handlers: Map = new Map(); - const mockSendRequest = vi.fn().mockResolvedValue({ action: 'cancel' }); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - const handler = handlers.get('trigger-url-elicitation')!; - const result = await handler( - { - url: 'https://example.com/verify', - message: 'Open this page to verify your identity', - elicitationId: 'elicitation-123', - errorPath: false, - }, - { sendRequest: mockSendRequest } - ); - - expect(result.content[0].text).toContain( - 'โš ๏ธ User cancelled the URL elicitation' - ); - }); - - it('should throw MCP error -32042 with a prerequisite elicitation pointing at a different URL when errorPath is true', async () => { - const handlers: Map = new Map(); - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - const handler = handlers.get('trigger-url-elicitation')!; - - expect.assertions(5); - - try { - await handler( - { - url: 'https://example.com/connect', - message: 'Authorization is required to continue.', - elicitationId: 'elicitation-xyz', - errorPath: true, - }, - {} - ); - } catch (error: any) { - expect(error.code).toBe(-32042); - const prerequisite = error.data.elicitations[0]; - expect(prerequisite.mode).toBe('url'); - // The prerequisite must NOT reuse the failing URL, otherwise the client - // would complete it, retry, hit the same error, and loop forever. - expect(prerequisite.url).toBe('https://modelcontextprotocol.io'); - expect(prerequisite.url).not.toBe('https://example.com/connect'); - // It carries its own elicitation id for the prerequisite itself. - expect(typeof prerequisite.elicitationId).toBe('string'); - } - }); - - it('should ignore errorPath and take the request path when the same call is retried after the prerequisite', async () => { - const handlers: Map = new Map(); - const mockSendRequest = vi.fn().mockResolvedValue({ action: 'accept' }); - - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ elicitation: { url: {} } })), - }, - } as unknown as McpServer; - - registerTriggerUrlElicitationTool(mockServer); - - const handler = handlers.get('trigger-url-elicitation')!; - // A real client retries with the *same* arguments and does not echo the - // prerequisite's elicitationId. Note these args omit elicitationId, so the - // correlation must rely on stable inputs (session + url), not a per-call - // random id. - const args = { - url: 'https://example.com/connect', - message: 'Authorization is required to continue.', - errorPath: true, - }; - const extra = { sessionId: 'session-1', sendRequest: mockSendRequest }; - - // First call: error path issues the prerequisite and throws -32042. - let prerequisiteUrl: string | undefined; - try { - await handler(args, extra); - throw new Error('expected first call to throw'); - } catch (error: any) { - expect(error.code).toBe(-32042); - prerequisiteUrl = error.data.elicitations[0].url; - expect(prerequisiteUrl).toBe('https://modelcontextprotocol.io'); - expect(mockSendRequest).not.toHaveBeenCalled(); - } - - // Plain retry with identical arguments: errorPath is ignored and the call - // proceeds via the request path instead of throwing the prerequisite again. - const result = await handler({ ...args }, extra); - - expect(mockSendRequest).toHaveBeenCalledWith( - expect.objectContaining({ - method: 'elicitation/create', - params: expect.objectContaining({ - mode: 'url', - url: 'https://example.com/connect', - }), - }), - expect.anything(), - expect.anything() - ); - expect(result.content[0].text).toContain( - 'โœ… User completed the URL elicitation flow.' - ); - }); - }); - - describe('get-roots-list', () => { - it('should not register when client does not support roots', () => { - const { mockServer } = createMockServer(); - registerGetRootsListTool(mockServer); - - expect(mockServer.registerTool).not.toHaveBeenCalled(); - }); - - it('should register when client supports roots', () => { - const handlers: Map = new Map(); - const mockServer = { - registerTool: vi.fn((name: string, config: any, handler: Function) => { - handlers.set(name, handler); - }), - server: { - getClientCapabilities: vi.fn(() => ({ roots: {} })), - }, - } as unknown as McpServer; - - registerGetRootsListTool(mockServer); - - expect(mockServer.registerTool).toHaveBeenCalledWith( - 'get-roots-list', - expect.objectContaining({ - title: 'Get Roots List Tool', - description: expect.stringContaining('roots'), - }), - expect.any(Function) - ); - }); - }); - - describe('simulate-research-query', () => { - function createMockServerWithTasks() { - const taskHandlers: Record = {}; - const mockServer = { - experimental: { - tasks: { - registerToolTask: vi.fn((_name: string, _config: any, handler: any) => { - Object.assign(taskHandlers, handler); - }), - }, - }, - server: { getClientCapabilities: vi.fn(() => ({ elicitation: {} })) }, - } as unknown as McpServer; - return { mockServer, taskHandlers }; - } - - function createMockTaskStore(taskId: string) { - return { - createTask: vi.fn().mockResolvedValue({ - taskId, - status: 'working', - createdAt: new Date().toISOString(), - lastUpdatedAt: new Date().toISOString(), - ttl: 300000, - pollInterval: 1000, - }), - updateTaskStatus: vi.fn().mockResolvedValue(undefined), - storeTaskResult: vi.fn().mockResolvedValue(undefined), - getTask: vi.fn(), - getTaskResult: vi.fn(), - }; - } - - it('should pass relatedTask to sendRequest when elicitation is triggered', async () => { - vi.useFakeTimers(); - - const { mockServer, taskHandlers } = createMockServerWithTasks(); - registerSimulateResearchQueryTool(mockServer); - - const mockTaskStore = createMockTaskStore('task-abc'); - const mockSendRequest = vi.fn().mockResolvedValue({ - action: 'accept', - content: { interpretation: 'technical' }, - }); - - await taskHandlers.createTask( - { topic: 'python', ambiguous: true }, - { taskStore: mockTaskStore, sendRequest: mockSendRequest } - ); - - await vi.runAllTimersAsync(); - vi.useRealTimers(); - - expect(mockSendRequest).toHaveBeenCalledWith( - expect.objectContaining({ method: 'elicitation/create' }), - expect.anything(), - expect.objectContaining({ relatedTask: { taskId: 'task-abc' } }) - ); - }); - - it('should complete without elicitation for non-ambiguous query', async () => { - vi.useFakeTimers(); - - const { mockServer, taskHandlers } = createMockServerWithTasks(); - registerSimulateResearchQueryTool(mockServer); - - const mockTaskStore = createMockTaskStore('task-def'); - const mockSendRequest = vi.fn(); - - await taskHandlers.createTask( - { topic: 'python', ambiguous: false }, - { taskStore: mockTaskStore, sendRequest: mockSendRequest } - ); - - await vi.runAllTimersAsync(); - vi.useRealTimers(); - - expect(mockSendRequest).not.toHaveBeenCalled(); - expect(mockTaskStore.storeTaskResult).toHaveBeenCalledWith( - 'task-def', 'completed', expect.anything() - ); - }); - }); - - describe('gzip-file-as-resource', () => { - it('should compress data URI and return resource link', async () => { - const registeredResources: any[] = []; - const mockServer = { - registerTool: vi.fn(), - registerResource: vi.fn((...args) => { - registeredResources.push(args); - }), - } as unknown as McpServer; - - // Get the handler - let handler: Function | null = null; - (mockServer.registerTool as any).mockImplementation( - (name: string, config: any, h: Function) => { - handler = h; - } - ); - - registerGZipFileAsResourceTool(mockServer); - - // Create a data URI with test content - const testContent = 'Hello, World!'; - const dataUri = `data:text/plain;base64,${Buffer.from(testContent).toString('base64')}`; - - const result = await handler!( - { name: 'test.txt.gz', data: dataUri, outputType: 'resourceLink' } - ); - - expect(result.content[0].type).toBe('resource_link'); - expect(result.content[0].uri).toContain('test.txt.gz'); - }); - - it('should return resource directly when outputType is resource', async () => { - const mockServer = { - registerTool: vi.fn(), - registerResource: vi.fn(), - } as unknown as McpServer; - - let handler: Function | null = null; - (mockServer.registerTool as any).mockImplementation( - (name: string, config: any, h: Function) => { - handler = h; - } - ); - - registerGZipFileAsResourceTool(mockServer); - - const testContent = 'Test content for compression'; - const dataUri = `data:text/plain;base64,${Buffer.from(testContent).toString('base64')}`; - - const result = await handler!( - { name: 'output.gz', data: dataUri, outputType: 'resource' } - ); - - expect(result.content[0].type).toBe('resource'); - expect(result.content[0].resource.mimeType).toBe('application/gzip'); - expect(result.content[0].resource.blob).toBeDefined(); - }); - - it('should reject unsupported URL protocols', async () => { - const mockServer = { - registerTool: vi.fn(), - registerResource: vi.fn(), - } as unknown as McpServer; - - let handler: Function | null = null; - (mockServer.registerTool as any).mockImplementation( - (name: string, config: any, h: Function) => { - handler = h; - } - ); - - registerGZipFileAsResourceTool(mockServer); - - await expect( - handler!({ name: 'test.gz', data: 'ftp://example.com/file.txt', outputType: 'resource' }) - ).rejects.toThrow('Unsupported URL protocol'); - }); - }); -}); diff --git a/src/everything/docs/architecture.md b/src/everything/docs/architecture.md deleted file mode 100644 index 728cfd4..0000000 --- a/src/everything/docs/architecture.md +++ /dev/null @@ -1,44 +0,0 @@ -# Everything Server โ€“ Architecture - -**Architecture -| [Project Structure](structure.md) -| [Startup Process](startup.md) -| [Server Features](features.md) -| [Extension Points](extension.md) -| [How It Works](how-it-works.md)** - -This documentation summarizes the current layout and runtime architecture of the `src/everything` package. -It explains how the server starts, how transports are wired, where tools, prompts, and resources are registered, and how to extend the system. - -## Highโ€‘level Overview - -### Purpose - -A minimal, modular MCP server showcasing core Model Context Protocol features. It exposes simple tools, prompts, and resources, and can be run over multiple transports (STDIO, SSE, and Streamable HTTP). - -### Design - -A small โ€œserver factoryโ€ constructs the MCP server and registers features. -Transports are separate entry points that create/connect the server and handle network concerns. -Tools, prompts, and resources are organized in their own submodules. - -### Multiโ€‘client - -The server supports multiple concurrent clients. Tracking per session data is demonstrated with -resource subscriptions and simulated logging. - -## Build and Distribution - -- TypeScript sources are compiled into `dist/` via `npm run build`. -- The `build` script copies `docs/` into `dist/` so instruction files ship alongside the compiled server. -- The CLI bin is configured in `package.json` as `mcp-server-everything` โ†’ `dist/index.js`. - -## [Project Structure](structure.md) - -## [Startup Process](startup.md) - -## [Server Features](features.md) - -## [Extension Points](extension.md) - -## [How It Works](how-it-works.md) diff --git a/src/everything/docs/extension.md b/src/everything/docs/extension.md deleted file mode 100644 index 1d77730..0000000 --- a/src/everything/docs/extension.md +++ /dev/null @@ -1,23 +0,0 @@ -# Everything Server - Extension Points - -**[Architecture](architecture.md) -| [Project Structure](structure.md) -| [Startup Process](startup.md) -| [Server Features](features.md) -| Extension Points -| [How It Works](how-it-works.md)** - -## Adding Tools - -- Create a new file under `tools/` with your `registerXTool(server)` function that registers the tool via `server.registerTool(...)`. -- Export and call it from `tools/index.ts` inside `registerTools(server)`. - -## Adding Prompts - -- Create a new file under `prompts/` with your `registerXPrompt(server)` function that registers the prompt via `server.registerPrompt(...)`. -- Export and call it from `prompts/index.ts` inside `registerPrompts(server)`. - -## Adding Resources - -- Create a new file under `resources/` with your `registerXResources(server)` function using `server.registerResource(...)` (optionally with `ResourceTemplate`). -- Export and call it from `resources/index.ts` inside `registerResources(server)`. diff --git a/src/everything/docs/features.md b/src/everything/docs/features.md deleted file mode 100644 index a5429ac..0000000 --- a/src/everything/docs/features.md +++ /dev/null @@ -1,105 +0,0 @@ -# Everything Server - Features - -**[Architecture](architecture.md) -| [Project Structure](structure.md) -| [Startup Process](startup.md) -| Server Features -| [Extension Points](extension.md) -| [How It Works](how-it-works.md)** - -## Tools - -- `echo` (tools/echo.ts): Echoes the provided `message: string`. Uses Zod to validate inputs. -- `get-annotated-message` (tools/get-annotated-message.ts): Returns a `text` message annotated with `priority` and `audience` based on `messageType` (`error`, `success`, or `debug`); can optionally include an annotated `image`. -- `get-env` (tools/get-env.ts): Returns all environment variables from the running process as pretty-printed JSON text. -- `get-resource-links` (tools/get-resource-links.ts): Returns an intro `text` block followed by multiple `resource_link` items. For a requested `count` (1โ€“10), alternates between dynamic Text and Blob resources using URIs from `resources/templates.ts`. -- `get-resource-reference` (tools/get-resource-reference.ts): Accepts `resourceType` (`text` or `blob`) and `resourceId` (positive integer). Returns a concrete `resource` content block (with its `uri`, `mimeType`, and data) with surrounding explanatory `text`. -- `get-roots-list` (tools/get-roots-list.ts): Returns the last list of roots sent by the client. -- `gzip-file-as-resource` (tools/gzip-file-as-resource.ts): Accepts a `name` and `data` (URL or data URI), fetches the data subject to size/time/domain constraints, compresses it, registers it as a session resource at `demo://resource/session/` with `mimeType: application/gzip`, and returns either a `resource_link` (default) or an inline `resource` depending on `outputType`. -- `get-structured-content` (tools/get-structured-content.ts): Demonstrates structured responses. Accepts `location` input and returns both backwardโ€‘compatible `content` (a `text` block containing JSON) and `structuredContent` validated by an `outputSchema` (temperature, conditions, humidity). -- `get-sum` (tools/get-sum.ts): For two numbers `a` and `b` calculates and returns their sum. Uses Zod to validate inputs. -- `get-tiny-image` (tools/get-tiny-image.ts): Returns a tiny PNG MCP logo as an `image` content item with brief descriptive text before and after. -- `trigger-long-running-operation` (tools/trigger-long-running-operation.ts): Simulates a multi-step operation over a given `duration` and number of `steps`; reports progress via `notifications/progress` when a `progressToken` is provided by the client. -- `toggle-simulated-logging` (tools/toggle-simulated-logging.ts): Starts or stops simulated, randomโ€‘leveled logging for the invoking session. Respects the clientโ€™s selected minimum logging level. -- `toggle-subscriber-updates` (tools/toggle-subscriber-updates.ts): Starts or stops simulated resource update notifications for URIs the invoking session has subscribed to. -- `trigger-elicitation-request` (tools/trigger-elicitation-request.ts): Issues an `elicitation/create` request using form-mode fields (strings, numbers, booleans, enums, and format validation) and returns the resulting action/content. -- `trigger-url-elicitation` (tools/trigger-url-elicitation.ts): Issues an `elicitation/create` request in URL mode (`mode: "url"`) with an `elicitationId`, or throws MCP error `-32042` (`UrlElicitationRequiredError`) when `errorPath=true`. On the error path the prerequisite elicitation it returns points at a different URL than the failing request (`https://modelcontextprotocol.io`); when the client satisfies it and retries the same call, the retry ignores `errorPath` and proceeds via the request path, so the client does not loop on the same error. The retry marker is one-shot per `(session, url, elicitationId)`: it is cleared on the recognized retry, so re-running the error path with identical arguments without an intervening prerequisite is treated as a retry and proceeds. Requires client capability `elicitation.url`. -- `trigger-sampling-request` (tools/trigger-sampling-request.ts): Issues a `sampling/createMessage` request to the client/LLM using provided `prompt` and optional generation controls; returns the LLM's response payload. -- `simulate-research-query` (tools/simulate-research-query.ts): Demonstrates MCP Tasks (SEP-1686) with a simulated multi-stage research operation. Accepts `topic` and `ambiguous` parameters. Returns a task that progresses through stages with status updates. If `ambiguous` is true and client supports elicitation, sends an elicitation request directly to gather clarification before completing. -- `trigger-sampling-request-async` (tools/trigger-sampling-request-async.ts): Demonstrates bidirectional tasks where the server sends a sampling request that the client executes as a background task. Server polls for status and retrieves the LLM result when complete. Requires client to support `tasks.requests.sampling.createMessage`. -- `trigger-elicitation-request-async` (tools/trigger-elicitation-request-async.ts): Demonstrates bidirectional tasks where the server sends an elicitation request that the client executes as a background task. Server polls while waiting for user input. Requires client to support `tasks.requests.elicitation.create`. - -## Prompts - -- `simple-prompt` (prompts/simple.ts): No-argument prompt that returns a static user message. -- `args-prompt` (prompts/args.ts): Two-argument prompt with `city` (required) and `state` (optional) used to compose a question. -- `completable-prompt` (prompts/completions.ts): Demonstrates argument auto-completions with the SDKโ€™s `completable` helper; `department` completions drive context-aware `name` suggestions. -- `resource-prompt` (prompts/resource.ts): Accepts `resourceType` ("Text" or "Blob") and `resourceId` (string convertible to integer) and returns messages that include an embedded dynamic resource of the selected type generated via `resources/templates.ts`. - -## Resources - -- Dynamic Text: `demo://resource/dynamic/text/{index}` (content generated on the fly) -- Dynamic Blob: `demo://resource/dynamic/blob/{index}` (base64 payload generated on the fly) -- Static Documents: `demo://resource/static/document/` (serves files from `src/everything/docs/` as static file-based resources) -- Session Scoped: `demo://resource/session/` (per-session resources registered dynamically; available only for the lifetime of the session) - -## Resource Subscriptions and Notifications - -- Simulated update notifications are optโ€‘in and off by default. -- Clients may subscribe/unsubscribe to resource URIs using the MCP `resources/subscribe` and `resources/unsubscribe` requests. -- Use the `toggle-subscriber-updates` tool to start/stop a perโ€‘session interval that emits `notifications/resources/updated { uri }` only for URIs that session has subscribed to. -- Multiple concurrent clients are supported; each clientโ€™s subscriptions are tracked per session and notifications are delivered independently via the server instance associated with that session. - -## Simulated Logging - -- Simulated logging is available but off by default. -- Use the `toggle-simulated-logging` tool to start/stop periodic log messages of varying levels (debug, info, notice, warning, error, critical, alert, emergency) per session. -- Clients can control the minimum level they receive via the standard MCP `logging/setLevel` request. - -## Tasks (SEP-1686) - -The server advertises support for MCP Tasks, enabling long-running operations with status tracking: - -- **Capabilities advertised**: `tasks.list`, `tasks.cancel`, `tasks.requests.tools.call` -- **Task Store**: Uses `InMemoryTaskStore` from SDK experimental for task lifecycle management -- **Message Queue**: Uses `InMemoryTaskMessageQueue` for task-related messaging - -### Task Lifecycle - -1. Client calls `tools/call` with `task: true` parameter -2. Server returns `CreateTaskResult` with `taskId` instead of immediate result -3. Client polls `tasks/get` to check status and receive `statusMessage` updates -4. When status is `completed`, client calls `tasks/result` to retrieve the final result - -### Task Statuses - -- `working`: Task is actively processing -- `input_required`: Task needs additional input (server sends elicitation request directly) -- `completed`: Task finished successfully -- `failed`: Task encountered an error -- `cancelled`: Task was cancelled by client - -### Demo Tools - -**Server-side tasks (client calls server):** -Use the `simulate-research-query` tool to exercise the full task lifecycle. Set `ambiguous: true` to trigger elicitation - the server will send an `elicitation/create` request directly and await the response before completing. - -**Client-side tasks (server calls client):** -Use `trigger-sampling-request-async` or `trigger-elicitation-request-async` to demonstrate bidirectional tasks where the server sends requests that the client executes as background tasks. These require the client to advertise `tasks.requests.sampling.createMessage` or `tasks.requests.elicitation.create` capabilities respectively. - -### Bidirectional Task Flow - -MCP Tasks are bidirectional - both server and client can be task executors: - -| Direction | Request Type | Task Executor | Demo Tool | -| ---------------- | ------------------------ | ------------- | ----------------------------------- | -| Client -> Server | `tools/call` | Server | `simulate-research-query` | -| Server -> Client | `sampling/createMessage` | Client | `trigger-sampling-request-async` | -| Server -> Client | `elicitation/create` | Client | `trigger-elicitation-request-async` | - -For client-side tasks: - -1. Server sends request with task metadata (e.g., `params.task.ttl`) -2. Client creates task and returns `CreateTaskResult` with `taskId` -3. Server polls `tasks/get` for status updates -4. When complete, server calls `tasks/result` to retrieve the result diff --git a/src/everything/docs/how-it-works.md b/src/everything/docs/how-it-works.md deleted file mode 100644 index 514c6f5..0000000 --- a/src/everything/docs/how-it-works.md +++ /dev/null @@ -1,45 +0,0 @@ -# Everything Server - How It Works - -**[Architecture](architecture.md) -| [Project Structure](structure.md) -| [Startup Process](startup.md) -| [Server Features](features.md) -| [Extension Points](extension.md) -| How It Works** - -# Conditional Tool Registration - -### Module: `server/index.ts` - -- Some tools require client support for the capability they demonstrate. These are: - - `get-roots-list` - - `trigger-elicitation-request` - - `trigger-sampling-request` -- Client capabilities aren't known until after initilization handshake is complete. -- Most tools are registered immediately during the Server Factory execution, prior to client connection. -- To defer registration of these commands until client capabilities are known, a `registerConditionalTools(server)` function is invoked from an `onintitialized` handler. - -## Resource Subscriptions - -### Module: `resources/subscriptions.ts` - -- Tracks subscribers per URI: `Map>`. -- Installs handlers via `setSubscriptionHandlers(server)` to process subscribe/unsubscribe requests and keep the map updated. -- Updates are started/stopped on demand by the `toggle-subscriber-updates` tool, which calls `beginSimulatedResourceUpdates(server, sessionId)` and `stopSimulatedResourceUpdates(sessionId)`. -- `cleanup(sessionId?)` calls `stopSimulatedResourceUpdates(sessionId)` to clear intervals and remove sessionโ€‘scoped state. - -## Sessionโ€‘scoped Resources - -### Module: `resources/session.ts` - -- `getSessionResourceURI(name: string)`: Builds a session resource URI: `demo://resource/session/`. -- `registerSessionResource(server, resource, type, payload)`: Registers a resource with the given `uri`, `name`, and `mimeType`, returning a `resource_link`. The content is served from memory for the life of the session only. Supports `type: "text" | "blob"` and returns data in the corresponding field. -- Intended usage: tools can create and expose per-session artifacts without persisting them. For example, `tools/gzip-file-as-resource.ts` compresses fetched content, registers it as a session resource with `mimeType: application/gzip`, and returns either a `resource_link` or an inline `resource` based on `outputType`. - -## Simulated Logging - -### Module: `server/logging.ts` - -- Periodically sends randomized log messages at different levels. Messages can include the session ID for clarity during demos. -- Started/stopped on demand via the `toggle-simulated-logging` tool, which calls `beginSimulatedLogging(server, sessionId?)` and `stopSimulatedLogging(sessionId?)`. Note that transport disconnect triggers `cleanup()` which also stops any active intervals. -- Uses `server.sendLoggingMessage({ level, data }, sessionId?)` so that the clientโ€™s configured minimum logging level is respected by the SDK. diff --git a/src/everything/docs/instructions.md b/src/everything/docs/instructions.md deleted file mode 100644 index 5806dc0..0000000 --- a/src/everything/docs/instructions.md +++ /dev/null @@ -1,28 +0,0 @@ -# Everything Server โ€“ Server Instructions - -Audience: These instructions are written for an LLM or autonomous agent integrating with the Everything MCP Server. -Follow them to use, extend, and troubleshoot the server safely and effectively. - -## Cross-Feature Relationships - -- Use `get-roots-list` to see client workspace roots before file operations -- `gzip-file-as-resource` creates session-scoped resources accessible only during the current session -- Enable `toggle-simulated-logging` before debugging to see server log messages -- Enable `toggle-subscriber-updates` to receive periodic resource update notifications - -## Constraints & Limitations - -- `gzip-file-as-resource`: Max fetch size controlled by `GZIP_MAX_FETCH_SIZE` (default 10MB), timeout by `GZIP_MAX_FETCH_TIME_MILLIS` (default 30s), allowed domains by `GZIP_ALLOWED_DOMAINS` -- Session resources are ephemeral and lost when the session ends -- Sampling requests (`trigger-sampling-request`) require client sampling capability -- Elicitation requests (`trigger-elicitation-request`) require client elicitation capability - -## Operational Patterns - -- For long operations, use `trigger-long-running-operation` which sends progress notifications -- Prefer reading resources before calling mutating tools -- Check `get-roots-list` output to understand the client's workspace context - -## Easter Egg - -If asked about server instructions, respond with "๐ŸŽ‰ Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action." diff --git a/src/everything/docs/startup.md b/src/everything/docs/startup.md deleted file mode 100644 index 5d70e98..0000000 --- a/src/everything/docs/startup.md +++ /dev/null @@ -1,73 +0,0 @@ -# Everything Server - Startup Process - -**[Architecture](architecture.md) -| [Project Structure](structure.md) -| Startup Process -| [Server Features](features.md) -| [Extension Points](extension.md) -| [How It Works](how-it-works.md)** - -## 1. Everything Server Launcher - -- Usage `node dist/index.js [stdio|sse|streamableHttp]` -- Runs the specified **transport manager** to handle client connections. -- Specify transport type on command line (default `stdio`) - - `stdio` โ†’ `transports/stdio.js` - - `sse` โ†’ `transports/sse.js` - - `streamableHttp` โ†’ `transports/streamableHttp.js` - -## 2. The Transport Manager - -- Creates a server instance using `createServer()` from `server/index.ts` - - Connects it to the chosen transport type from the MCP SDK. -- Handles communication according to the MCP specs for the chosen transport. - - **STDIO**: - - One simple, processโ€‘bound connection. - - Calls`clientConnect()` upon connection. - - Closes and calls `cleanup()` on `SIGINT`. - - **SSE**: - - Supports multiple client connections. - - Client transports are mapped to `sessionId`; - - Calls `clientConnect(sessionId)` upon connection. - - Hooks serverโ€™s `onclose` to clean and remove session. - - Exposes - - `/sse` **GET** (SSE stream) - - `/message` **POST** (JSONโ€‘RPC messages) - - **Streamable HTTP**: - - Supports multiple client connections. - - Client transports are mapped to `sessionId`; - - Calls `clientConnect(sessionId)` upon connection. - - Exposes `/mcp` for - - **POST** (JSONโ€‘RPC messages) - - **GET** (SSE stream) - - **DELETE** (termination) - - Uses an event store for resumability and stores transports by `sessionId`. - - Calls `cleanup(sessionId)` on **DELETE**. - -## 3. The Server Factory - -- Invoke `createServer()` from `server/index.ts` -- Creates a new `McpServer` instance with - - **Capabilities**: - - `tools: {}` - - `logging: {}` - - `prompts: {}` - - `resources: { subscribe: true }` - - **Server Instructions** - - Loaded from the docs folder (`instructions.md`). - - **Registrations** - - Registers **tools** via `registerTools(server)`. - - Registers **resources** via `registerResources(server)`. - - Registers **prompts** via `registerPrompts(server)`. - - **Other Request Handlers** - - Sets up resource subscription handlers via `setSubscriptionHandlers(server)`. - - Roots list change handler is added post-connection via - - **Returns** - - The `McpServer` instance - - A `clientConnect(sessionId)` callback that enables post-connection setup - - A `cleanup(sessionId?)` callback that stops any active intervals and removes any sessionโ€‘scoped state - -## Enabling Multiple Clients - -Some of the transport managers defined in the `transports` folder can support multiple clients. -In order to do so, they must map certain data to a session identifier. diff --git a/src/everything/docs/structure.md b/src/everything/docs/structure.md deleted file mode 100644 index ff1e67d..0000000 --- a/src/everything/docs/structure.md +++ /dev/null @@ -1,204 +0,0 @@ -# Everything Server - Project Structure - -**[Architecture](architecture.md) -| Project Structure -| [Startup Process](startup.md) -| [Server Features](features.md) -| [Extension Points](extension.md) -| [How It Works](how-it-works.md)** - -``` -src/everything - โ”œโ”€โ”€ index.ts - โ”œโ”€โ”€ AGENTS.md - โ”œโ”€โ”€ package.json - โ”œโ”€โ”€ docs - โ”‚ โ”œโ”€โ”€ architecture.md - โ”‚ โ”œโ”€โ”€ extension.md - โ”‚ โ”œโ”€โ”€ features.md - โ”‚ โ”œโ”€โ”€ how-it-works.md - โ”‚ โ”œโ”€โ”€ instructions.md - โ”‚ โ”œโ”€โ”€ startup.md - โ”‚ โ””โ”€โ”€ structure.md - โ”œโ”€โ”€ prompts - โ”‚ โ”œโ”€โ”€ index.ts - โ”‚ โ”œโ”€โ”€ args.ts - โ”‚ โ”œโ”€โ”€ completions.ts - โ”‚ โ”œโ”€โ”€ simple.ts - โ”‚ โ””โ”€โ”€ resource.ts - โ”œโ”€โ”€ resources - โ”‚ โ”œโ”€โ”€ index.ts - โ”‚ โ”œโ”€โ”€ files.ts - โ”‚ โ”œโ”€โ”€ session.ts - โ”‚ โ”œโ”€โ”€ subscriptions.ts - โ”‚ โ””โ”€โ”€ templates.ts - โ”œโ”€โ”€ server - โ”‚ โ”œโ”€โ”€ index.ts - โ”‚ โ”œโ”€โ”€ logging.ts - โ”‚ โ””โ”€โ”€ roots.ts - โ”œโ”€โ”€ tools - โ”‚ โ”œโ”€โ”€ index.ts - โ”‚ โ”œโ”€โ”€ echo.ts - โ”‚ โ”œโ”€โ”€ get-annotated-message.ts - โ”‚ โ”œโ”€โ”€ get-env.ts - โ”‚ โ”œโ”€โ”€ get-resource-links.ts - โ”‚ โ”œโ”€โ”€ get-resource-reference.ts - โ”‚ โ”œโ”€โ”€ get-roots-list.ts - โ”‚ โ”œโ”€โ”€ get-structured-content.ts - โ”‚ โ”œโ”€โ”€ get-sum.ts - โ”‚ โ”œโ”€โ”€ get-tiny-image.ts - โ”‚ โ”œโ”€โ”€ gzip-file-as-resource.ts - โ”‚ โ”œโ”€โ”€ simulate-research-query.ts - โ”‚ โ”œโ”€โ”€ toggle-simulated-logging.ts - โ”‚ โ”œโ”€โ”€ toggle-subscriber-updates.ts - โ”‚ โ”œโ”€โ”€ trigger-elicitation-request.ts - โ”‚ โ”œโ”€โ”€ trigger-elicitation-request-async.ts - โ”‚ โ”œโ”€โ”€ trigger-long-running-operation.ts - โ”‚ โ”œโ”€โ”€ trigger-sampling-request.ts - โ”‚ โ”œโ”€โ”€ trigger-sampling-request-async.ts - โ”‚ โ””โ”€โ”€ trigger-url-elicitation.ts - โ””โ”€โ”€ transports - โ”œโ”€โ”€ sse.ts - โ”œโ”€โ”€ stdio.ts - โ””โ”€โ”€ streamableHttp.ts -``` - -# Project Contents - -## `src/everything`: - -### `index.ts` - -- CLI entry point that selects and runs a specific transport module based on the first CLI argument: `stdio`, `sse`, or `streamableHttp`. - -### `AGENTS.md` - -- Directions for Agents/LLMs explaining coding guidelines and how to appropriately extend the server. - -### `package.json` - -- Package metadata and scripts: - - `build`: TypeScript compile to `dist/`, copies `docs/` into `dist/` and marks the compiled entry scripts as executable. - - `start:stdio`, `start:sse`, `start:streamableHttp`: Run built transports from `dist/`. -- Declares dependencies on `@modelcontextprotocol/sdk`, `express`, `cors`, `zod`, etc. - -### `docs/` - -- `architecture.md` - - Describes the server architecture, including transport layers, session initialization, and primitive registration. -- `extension.md` - - Explains how to extend the server with new tools, prompts, resources, or transports. -- `features.md` - - A comprehensive reference listing all implemented prompts, resources, tools, and protocol behaviors. -- `how-it-works.md` - - Explains how SSE and Streamable HTTP transports are set up and how sessions are managed. -- `instructions.md` - - Humanโ€‘readable instructions intended to be passed to the client/LLM as guidance on server use. Loaded by the server at startup and returned in the initialize exchange. -- `startup.md` - - Describes the server startup sequence, environment checks, and initialization. -- `structure.md` - - This document. - -### `prompts/` - -- `index.ts` - - `registerPrompts(server)` orchestrator; delegates to prompt factory/registration methods from in individual prompt files. -- `simple.ts` - - Registers `simple-prompt`: a prompt with no arguments that returns a single user message. -- `args.ts` - - Registers `args-prompt`: a prompt with two arguments (`city` required, `state` optional) used to compose a message. -- `completions.ts` - - Registers `completable-prompt`: a prompt whose arguments support server-driven completions using the SDKโ€™s `completable(...)` helper (e.g., completing `department` and context-aware `name`). -- `resource.ts` - - Exposes `registerEmbeddedResourcePrompt(server)` which registers `resource-prompt` โ€” a prompt that accepts `resourceType` ("Text" or "Blob") and `resourceId` (integer), and embeds a dynamically generated resource of the requested type within the returned messages. Internally reuses helpers from `resources/templates.ts`. - -### `resources/` - -- `index.ts` - - `registerResources(server)` orchestrator; delegates to resource factory/registration methods from individual resource files. -- `templates.ts` - - Registers two dynamic, templateโ€‘driven resources using `ResourceTemplate`: - - Text: `demo://resource/dynamic/text/{index}` (MIME: `text/plain`) - - Blob: `demo://resource/dynamic/blob/{index}` (MIME: `application/octet-stream`, Base64 payload) - - The `{index}` path variable must be a finite positive integer. Content is generated on demand with a timestamp. - - Exposes helpers `textResource(uri, index)`, `textResourceUri(index)`, `blobResource(uri, index)`, and `blobResourceUri(index)` so other modules can construct and embed dynamic resources directly (e.g., from prompts). -- `files.ts` - - Registers static file-based resources for each file in the `docs/` folder. - - URIs follow the pattern: `demo://resource/static/document/`. - - Serves markdown files as `text/markdown`, `.txt` as `text/plain`, `.json` as `application/json`, others default to `text/plain`. - -### `server/` - -- `index.ts` - - Server factory that creates an `McpServer` with declared capabilities, loads server instructions, and registers tools, prompts, and resources. - - Sets resource subscription handlers via `setSubscriptionHandlers(server)`. - - Exposes `{ server, cleanup }` to the chosen transport. Cleanup stops any running intervals in the server when the transport disconnects. -- `logging.ts` - - Implements simulated logging. Periodically sends randomized log messages at various levels to the connected client session. Started/stopped on demand via a dedicated tool. - -### `tools/` - -- `index.ts` - - `registerTools(server)` orchestrator; delegates to tool factory/registration methods in individual tool files. -- `echo.ts` - - Registers an `echo` tool that takes a message and returns `Echo: {message}`. -- `get-annotated-message.ts` - - Registers a `get-annotated-message` tool which demonstrates content-level annotations. Emits a primary `text` message with content `annotations` (`priority`, `audience`) that vary by `messageType` (`"error" | "success" | "debug"`), and optionally includes an annotated `image` (tiny PNG) when `includeImage` is true. All tools in this server include tool-level annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`). -- `get-env.ts` - - Registers a `get-env` tool that returns the current process environment variables as formatted JSON text; useful for debugging configuration. -- `get-resource-links.ts` - - Registers a `get-resource-links` tool that returns an intro `text` block followed by multiple `resource_link` items. -- `get-resource-reference.ts` - - Registers a `get-resource-reference` tool that returns a reference for a selected dynamic resource. -- `get-roots-list.ts` - - Registers a `get-roots-list` tool that returns the last list of roots sent by the client. -- `gzip-file-as-resource.ts` - - Registers a `gzip-file-as-resource` tool that fetches content from a URL or data URI, compresses it, and then either: - - returns a `resource_link` to a session-scoped resource (default), or - - returns an inline `resource` with the gzipped data. The resource will be still discoverable for the duration of the session via `resources/list`. - - Uses `resources/session.ts` to register the gzipped blob as a per-session resource at a URI like `demo://resource/session/` with `mimeType: application/gzip`. - - Environment controls: - - `GZIP_MAX_FETCH_SIZE` (bytes, default 10 MiB) - - `GZIP_MAX_FETCH_TIME_MILLIS` (ms, default 30000) - - `GZIP_ALLOWED_DOMAINS` (comma-separated allowlist; empty means all domains allowed) -- `simulate-research-query.ts` - - Registers a `simulate-research-query` task-based tool that demonstrates the MCP Tasks feature (SEP-1686). Simulates a multi-stage research operation with progress updates. If the query is marked as ambiguous and the client supports elicitation, it pauses mid-execution to request clarification via `elicitation/create`. Uses `server.experimental.tasks.registerToolTask()` with `execution: { taskSupport: "required" }`. -- `trigger-elicitation-request.ts` - - Registers a `trigger-elicitation-request` tool that sends an `elicitation/create` request to the client/LLM and returns the elicitation result. -- `trigger-url-elicitation.ts` - - Registers a `trigger-url-elicitation` tool that either sends an out-of-band URL-mode `elicitation/create` request (`mode: "url"`) including an `elicitationId` (request path) or throws `UrlElicitationRequiredError` (`-32042`) for client-handled URL elicitation (error path). On the error path the carried prerequisite elicitation points at a different URL than the failing one (`https://modelcontextprotocol.io`), and when the client satisfies it and retries the same call, the retry ignores `errorPath` and proceeds via the request path โ€” so the client does not loop on the same error. -- `trigger-elicitation-request-async.ts` - - Registers a `trigger-elicitation-request-async` tool that demonstrates bidirectional MCP tasks for elicitation. Sends an elicitation request with task metadata, then polls the client's `tasks/get` endpoint for completion status before fetching the final result. -- `trigger-sampling-request.ts` - - Registers a `trigger-sampling-request` tool that sends a `sampling/createMessage` request to the client/LLM and returns the sampling result. -- `trigger-sampling-request-async.ts` - - Registers a `trigger-sampling-request-async` tool that demonstrates bidirectional MCP tasks for sampling. Sends a sampling request with task metadata, then polls the client's `tasks/get` endpoint for completion status before fetching the final result. -- `get-structured-content.ts` - - Registers a `get-structured-content` tool that demonstrates structuredContent block responses. -- `get-sum.ts` - - Registers a `get-sum` tool with a Zod input schema that sums two numbers `a` and `b` and returns the result. -- `get-tiny-image.ts` - - Registers a `get-tiny-image` tool, which returns a tiny PNG MCP logo as an `image` content item, along with surrounding descriptive `text` items. -- `trigger-long-running-operation.ts` - - Registers a `trigger-long-running-operation` tool that simulates a long-running task over a specified `duration` (seconds) and number of `steps`; emits `notifications/progress` updates when the client supplies a `progressToken`. -- `toggle-simulated-logging.ts` - - Registers a `toggle-simulated-logging` tool, which starts or stops simulated logging for the invoking session. -- `toggle-subscriber-updates.ts` - - Registers a `toggle-subscriber-updates` tool, which starts or stops simulated resource subscription update checks for the invoking session. - -### `transports/` - -- `stdio.ts` - - Starts a `StdioServerTransport`, created the server via `createServer()`, and connects it. - - Handles `SIGINT` to close cleanly and calls `cleanup()` to remove any live intervals. -- `sse.ts` - - Express server exposing: - - `GET /sse` to establish an SSE connection per session. - - `POST /message` for client messages. - - Manages multiple connected clients via a transport map. - - Starts an `SSEServerTransport`, created the server via `createServer()`, and connects it to a new transport. - - On server disconnect, calls `cleanup()` to remove any live intervals. -- `streamableHttp.ts` - - Express server exposing a single `/mcp` endpoint for POST (JSONโ€‘RPC), GET (SSE stream), and DELETE (session termination) using `StreamableHTTPServerTransport`. - - Uses an `InMemoryEventStore` for resumable sessions and tracks transports by `sessionId`. - - Connects a fresh server instance on initialization POST and reuses the transport for subsequent requests. diff --git a/src/everything/everything.ts b/src/everything/everything.ts new file mode 100644 index 0000000..a31cd7f --- /dev/null +++ b/src/everything/everything.ts @@ -0,0 +1,441 @@ +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { + CallToolRequestSchema, + CreateMessageRequest, + CreateMessageResultSchema, + GetPromptRequestSchema, + ListPromptsRequestSchema, + ListResourcesRequestSchema, + ListResourceTemplatesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, + Resource, + SetLevelRequestSchema, + SubscribeRequestSchema, + Tool, + ToolSchema, + UnsubscribeRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; +import { zodToJsonSchema } from "zod-to-json-schema"; + +const ToolInputSchema = ToolSchema.shape.inputSchema; +type ToolInput = z.infer; + +/* Input schemas for tools implemented in this server */ +const EchoSchema = z.object({ + message: z.string().describe("Message to echo"), +}); + +const AddSchema = z.object({ + a: z.number().describe("First number"), + b: z.number().describe("Second number"), +}); + +const LongRunningOperationSchema = z.object({ + duration: z + .number() + .default(10) + .describe("Duration of the operation in seconds"), + steps: z.number().default(5).describe("Number of steps in the operation"), +}); + +const SampleLLMSchema = z.object({ + prompt: z.string().describe("The prompt to send to the LLM"), + maxTokens: z + .number() + .default(100) + .describe("Maximum number of tokens to generate"), +}); + +const GetTinyImageSchema = z.object({}); + +enum ToolName { + ECHO = "echo", + ADD = "add", + LONG_RUNNING_OPERATION = "longRunningOperation", + SAMPLE_LLM = "sampleLLM", + GET_TINY_IMAGE = "getTinyImage", +} + +enum PromptName { + SIMPLE = "simple_prompt", + COMPLEX = "complex_prompt", +} + +export const createServer = () => { + const server = new Server( + { + name: "example-servers/everything", + version: "1.0.0", + }, + { + capabilities: { + prompts: {}, + resources: { subscribe: true }, + tools: {}, + logging: {}, + }, + }, + ); + + let subscriptions: Set = new Set(); + let updateInterval: NodeJS.Timeout | undefined; + + // Set up update interval for subscribed resources + updateInterval = setInterval(() => { + for (const uri of subscriptions) { + server.notification({ + method: "notifications/resources/updated", + params: { uri }, + }); + } + }, 5000); + + // Helper method to request sampling from client + const requestSampling = async ( + context: string, + uri: string, + maxTokens: number = 100, + ) => { + const request: CreateMessageRequest = { + method: "sampling/createMessage", + params: { + messages: [ + { + role: "user", + content: { + type: "text", + text: `Resource ${uri} context: ${context}`, + }, + }, + ], + systemPrompt: "You are a helpful test server.", + maxTokens, + temperature: 0.7, + includeContext: "thisServer", + }, + }; + + return await server.request(request, CreateMessageResultSchema); + }; + + const ALL_RESOURCES: Resource[] = Array.from({ length: 100 }, (_, i) => { + const uri = `test://static/resource/${i + 1}`; + if (i % 2 === 0) { + return { + uri, + name: `Resource ${i + 1}`, + mimeType: "text/plain", + text: `Resource ${i + 1}: This is a plaintext resource`, + }; + } else { + const buffer = Buffer.from(`Resource ${i + 1}: This is a base64 blob`); + return { + uri, + name: `Resource ${i + 1}`, + mimeType: "application/octet-stream", + blob: buffer.toString("base64"), + }; + } + }); + + const PAGE_SIZE = 10; + + server.setRequestHandler(ListResourcesRequestSchema, async (request) => { + const cursor = request.params?.cursor; + let startIndex = 0; + + if (cursor) { + const decodedCursor = parseInt(atob(cursor), 10); + if (!isNaN(decodedCursor)) { + startIndex = decodedCursor; + } + } + + const endIndex = Math.min(startIndex + PAGE_SIZE, ALL_RESOURCES.length); + const resources = ALL_RESOURCES.slice(startIndex, endIndex); + + let nextCursor: string | undefined; + if (endIndex < ALL_RESOURCES.length) { + nextCursor = btoa(endIndex.toString()); + } + + return { + resources, + nextCursor, + }; + }); + + server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { + return { + resourceTemplates: [ + { + uriTemplate: "test://static/resource/{id}", + name: "Static Resource", + description: "A static resource with a numeric ID", + }, + ], + }; + }); + + server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const uri = request.params.uri; + + if (uri.startsWith("test://static/resource/")) { + const index = parseInt(uri.split("/").pop() ?? "", 10) - 1; + if (index >= 0 && index < ALL_RESOURCES.length) { + const resource = ALL_RESOURCES[index]; + return { + contents: [resource], + }; + } + } + + throw new Error(`Unknown resource: ${uri}`); + }); + + server.setRequestHandler(SubscribeRequestSchema, async (request) => { + const { uri } = request.params; + subscriptions.add(uri); + + // Request sampling from client when someone subscribes + await requestSampling("A new subscription was started", uri); + return {}; + }); + + server.setRequestHandler(UnsubscribeRequestSchema, async (request) => { + subscriptions.delete(request.params.uri); + return {}; + }); + + server.setRequestHandler(ListPromptsRequestSchema, async () => { + return { + prompts: [ + { + name: PromptName.SIMPLE, + description: "A prompt without arguments", + }, + { + name: PromptName.COMPLEX, + description: "A prompt with arguments", + arguments: [ + { + name: "temperature", + description: "Temperature setting", + required: true, + }, + { + name: "style", + description: "Output style", + required: false, + }, + ], + }, + ], + }; + }); + + server.setRequestHandler(GetPromptRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + if (name === PromptName.SIMPLE) { + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: "This is a simple prompt without arguments.", + }, + }, + ], + }; + } + + if (name === PromptName.COMPLEX) { + return { + messages: [ + { + role: "user", + content: { + type: "text", + text: `This is a complex prompt with arguments: temperature=${args?.temperature}, style=${args?.style}`, + }, + }, + { + role: "assistant", + content: { + type: "text", + text: "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?", + }, + }, + { + role: "user", + content: { + type: "image", + data: MCP_TINY_IMAGE, + mimeType: "image/png", + }, + }, + ], + }; + } + + throw new Error(`Unknown prompt: ${name}`); + }); + + server.setRequestHandler(ListToolsRequestSchema, async () => { + const tools: Tool[] = [ + { + name: ToolName.ECHO, + description: "Echoes back the input", + inputSchema: zodToJsonSchema(EchoSchema) as ToolInput, + }, + { + name: ToolName.ADD, + description: "Adds two numbers", + inputSchema: zodToJsonSchema(AddSchema) as ToolInput, + }, + { + name: ToolName.LONG_RUNNING_OPERATION, + description: + "Demonstrates a long running operation with progress updates", + inputSchema: zodToJsonSchema(LongRunningOperationSchema) as ToolInput, + }, + { + name: ToolName.SAMPLE_LLM, + description: "Samples from an LLM using MCP's sampling feature", + inputSchema: zodToJsonSchema(SampleLLMSchema) as ToolInput, + }, + { + name: ToolName.GET_TINY_IMAGE, + description: "Returns the MCP_TINY_IMAGE", + inputSchema: zodToJsonSchema(GetTinyImageSchema) as ToolInput, + }, + ]; + + return { tools }; + }); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + if (name === ToolName.ECHO) { + const validatedArgs = EchoSchema.parse(args); + return { + content: [{ type: "text", text: `Echo: ${validatedArgs.message}` }], + }; + } + + if (name === ToolName.ADD) { + const validatedArgs = AddSchema.parse(args); + const sum = validatedArgs.a + validatedArgs.b; + return { + content: [ + { + type: "text", + text: `The sum of ${validatedArgs.a} and ${validatedArgs.b} is ${sum}.`, + }, + ], + }; + } + + if (name === ToolName.LONG_RUNNING_OPERATION) { + const validatedArgs = LongRunningOperationSchema.parse(args); + const { duration, steps } = validatedArgs; + const stepDuration = duration / steps; + const progressToken = request.params._meta?.progressToken; + + for (let i = 1; i < steps + 1; i++) { + await new Promise((resolve) => + setTimeout(resolve, stepDuration * 1000), + ); + + if (progressToken !== undefined) { + await server.notification({ + method: "notifications/progress", + params: { + progress: i, + total: steps, + progressToken, + }, + }); + } + } + + return { + content: [ + { + type: "text", + text: `Long running operation completed. Duration: ${duration} seconds, Steps: ${steps}.`, + }, + ], + }; + } + + if (name === ToolName.SAMPLE_LLM) { + const validatedArgs = SampleLLMSchema.parse(args); + const { prompt, maxTokens } = validatedArgs; + + const result = await requestSampling( + prompt, + ToolName.SAMPLE_LLM, + maxTokens, + ); + return { + content: [{ type: "text", text: `LLM sampling result: ${result}` }], + }; + } + + if (name === ToolName.GET_TINY_IMAGE) { + GetTinyImageSchema.parse(args); + return { + content: [ + { + type: "text", + text: "This is a tiny image:", + }, + { + type: "image", + data: MCP_TINY_IMAGE, + mimeType: "image/png", + }, + { + type: "text", + text: "The image above is the MCP tiny image.", + }, + ], + }; + } + + throw new Error(`Unknown tool: ${name}`); + }); + + server.setRequestHandler(SetLevelRequestSchema, async (request) => { + const { level } = request.params; + + // Demonstrate different log levels + await server.notification({ + method: "notifications/message", + params: { + level: "debug", + logger: "test-server", + data: `Logging level set to: ${level}`, + }, + }); + + return {}; + }); + + const cleanup = async () => { + if (updateInterval) { + clearInterval(updateInterval); + } + }; + + return { server, cleanup }; +}; + +const MCP_TINY_IMAGE = + "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAKsGlDQ1BJQ0MgUHJvZmlsZQAASImVlwdUU+kSgOfe9JDQEiIgJfQmSCeAlBBaAAXpYCMkAUKJMRBU7MriClZURLCs6KqIgo0idizYFsWC3QVZBNR1sWDDlXeBQ9jdd9575805c+a7c+efmf+e/z9nLgCdKZDJMlF1gCxpjjwyyI8dn5DIJvUABRiY0kBdIMyWcSMiwgCTUft3+dgGyJC9YzuU69/f/1fREImzhQBIBMbJomxhFsbHMe0TyuQ5ALg9mN9kbo5siK9gzJRjDWL8ZIhTR7hviJOHGY8fjomO5GGsDUCmCQTyVACaKeZn5wpTsTw0f4ztpSKJFGPsGbyzsmaLMMbqgiUWI8N4KD8n+S95Uv+WM1mZUyBIVfLIXoaF7C/JlmUK5v+fn+N/S1amYrSGOaa0NHlwJGaxvpAHGbNDlSxNnhI+yhLRcPwwpymCY0ZZmM1LHGWRwD9UuTZzStgop0gC+co8OfzoURZnB0SNsnx2pLJWipzHHWWBfKyuIiNG6U8T85X589Ki40Y5VxI7ZZSzM6JCx2J4Sr9cEansXywN8hurG6jce1b2X/Yr4SvX5qRFByv3LhjrXyzljuXMjlf2JhL7B4zFxCjjZTl+ylqyzAhlvDgzSOnPzo1Srs3BDuTY2gjlN0wXhESMMoRBELAhBjIhB+QggECQgBTEOeJ5Q2cUeLNl8+WS1LQcNhe7ZWI2Xyq0m8B2tHd0Bhi6syNH4j1r+C4irGtjvhWVAF4nBgcHT475Qm4BHEkCoNaO+SxnAKh3A1w5JVTIc0d8Q9cJCEAFNWCCDhiACViCLTiCK3iCLwRACIRDNCTATBBCGmRhnc+FhbAMCqAI1sNmKIOdsBv2wyE4CvVwCs7DZbgOt+AePIZ26IJX0AcfYQBBEBJCRxiIDmKImCE2iCPCQbyRACQMiUQSkCQkFZEiCmQhsgIpQoqRMmQXUokcQU4g55GrSCvyEOlAepF3yFcUh9JQJqqPmqMTUQ7KRUPRaHQGmorOQfPQfHQtWopWoAfROvQ8eh29h7ajr9B+HOBUcCycEc4Wx8HxcOG4RFwKTo5bjCvEleAqcNW4Rlwz7g6uHfca9wVPxDPwbLwt3hMfjI/BC/Fz8Ivxq/Fl+P34OvxF/B18B74P/51AJ+gRbAgeBD4hnpBKmEsoIJQQ9hJqCZcI9whdhI9EIpFFtCC6EYOJCcR04gLiauJ2Yg3xHLGV2EnsJ5FIOiQbkhcpnCQg5ZAKSFtJB0lnSbdJXaTPZBWyIdmRHEhOJEvJy8kl5APkM+Tb5G7yAEWdYkbxoIRTRJT5lHWUPZRGyk1KF2WAqkG1oHpRo6np1GXUUmo19RL1CfW9ioqKsYq7ylQVicpSlVKVwypXVDpUvtA0adY0Hm06TUFbS9tHO0d7SHtPp9PN6b70RHoOfS29kn6B/oz+WZWhaqfKVxWpLlEtV61Tva36Ro2iZqbGVZuplqdWonZM7abaa3WKurk6T12gvli9XP2E+n31fg2GhoNGuEaWxmqNAxpXNXo0SZrmmgGaIs18zd2aFzQ7GTiGCYPHEDJWMPYwLjG6mESmBZPPTGcWMQ8xW5h9WppazlqxWvO0yrVOa7WzcCxzFp+VyVrHOspqY30dpz+OO048btW46nG3x33SHq/tqy3WLtSu0b6n/VWHrROgk6GzQade56kuXtdad6ruXN0dupd0X49njvccLxxfOP7o+Ed6qJ61XqTeAr3dejf0+vUN9IP0Zfpb9S/ovzZgGfgapBtsMjhj0GvIMPQ2lBhuMjxr+JKtxeayM9ml7IvsPiM9o2AjhdEuoxajAWML4xjj5cY1xk9NqCYckxSTTSZNJn2mhqaTTReaVpk+MqOYcczSzLaYNZt9MrcwjzNfaV5v3mOhbcG3yLOosnhiSbf0sZxjWWF514poxbHKsNpudcsatXaxTrMut75pg9q42khsttu0TiBMcJ8gnVAx4b4tzZZrm2tbZdthx7ILs1tuV2/3ZqLpxMSJGyY2T/xu72Kfab/H/rGDpkOIw3KHRod3jtaOQsdyx7tOdKdApyVODU5vnW2cxc47nB+4MFwmu6x0aXL509XNVe5a7drrZuqW5LbN7T6HyYngrOZccSe4+7kvcT/l/sXD1SPH46jHH562nhmeBzx7JllMEk/aM6nTy9hL4LXLq92b7Z3k/ZN3u4+Rj8Cnwue5r4mvyHevbzfXipvOPch942fvJ/er9fvE8+At4p3zx/kH+Rf6twRoBsQElAU8CzQOTA2sCuwLcglaEHQumBAcGrwh+D5fny/kV/L7QtxCFoVcDKWFRoWWhT4Psw6ThzVORieHTN44+ckUsynSKfXhEM4P3xj+NMIiYk7EyanEqRFTy6e+iHSIXBjZHMWImhV1IOpjtF/0uujHMZYxipimWLXY6bGVsZ/i/OOK49rjJ8Yvir+eoJsgSWhIJCXGJu5N7J8WMG3ztK7pLtMLprfNsJgxb8bVmbozM2eenqU2SzDrWBIhKS7pQNI3QbigQtCfzE/eltwn5Am3CF+JfEWbRL1iL3GxuDvFK6U4pSfVK3Vjam+aT1pJ2msJT1ImeZsenL4z/VNGeMa+jMHMuMyaLHJWUtYJqaY0Q3pxtsHsebNbZTayAln7HI85m+f0yUPle7OR7BnZDTlMbDi6obBU/KDoyPXOLc/9PDd27rF5GvOk827Mt56/an53XmDezwvwC4QLmhYaLVy2sGMRd9Guxcji5MVNS0yW5C/pWhq0dP8y6rKMZb8st19evPzDirgVjfn6+UvzO38I+qGqQLVAXnB/pefKnT/if5T82LLKadXWVd8LRYXXiuyLSoq+rRauvrbGYU3pmsG1KWtb1rmu27GeuF66vm2Dz4b9xRrFecWdGydvrNvE3lS46cPmWZuvljiX7NxC3aLY0l4aVtqw1XTr+q3fytLK7pX7ldds09u2atun7aLtt3f47qjeqb+zaOfXnyQ/PdgVtKuuwryiZDdxd+7uF3ti9zT/zPm5cq/u3qK9f+6T7mvfH7n/YqVbZeUBvQPrqtAqRVXvwekHbx3yP9RQbVu9q4ZVU3QYDisOvzySdKTtaOjRpmOcY9XHzY5vq2XUFtYhdfPr+urT6tsbEhpaT4ScaGr0bKw9aXdy3ymjU+WntU6vO0M9k39m8Gze2f5zsnOvz6ee72ya1fT4QvyFuxenXmy5FHrpyuXAyxeauc1nr3hdOXXV4+qJa5xr9dddr9fdcLlR+4vLL7Utri11N91uNtzyv9XYOqn1zG2f2+fv+N+5fJd/9/q9Kfda22LaHtyffr/9gehBz8PMh28f5T4aeLz0CeFJ4VP1pyXP9J5V/Gr1a027a/vpDv+OG8+jnj/uFHa++i37t29d+S/oL0q6Dbsrexx7TvUG9t56Oe1l1yvZq4HXBb9r/L7tjeWb43/4/nGjL76v66387eC71e913u/74PyhqT+i/9nHrI8Dnwo/63ze/4Xzpflr3NfugbnfSN9K/7T6s/F76Pcng1mDgzKBXDA8CuAwRVNSAN7tA6AnADCwGYI6bWSmHhZk5D9gmOA/8cjcPSyuANWYGRqNeOcADmNqvhRAzRdgaCyK9gXUyUmpo/Pv8Kw+JAbYv8K0HECi2x6tebQU/iEjc/xf+v6nBWXWv9l/AV0EC6JTIblRAAAAeGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAJAAAAABAAAAkAAAAAEAAqACAAQAAAABAAAAFKADAAQAAAABAAAAFAAAAAAXNii1AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB82lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjE0NDwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+MTQ0PC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KReh49gAAAjRJREFUOBGFlD2vMUEUx2clvoNCcW8hCqFAo1dKhEQpvsF9KrWEBh/ALbQ0KkInBI3SWyGPCCJEQliXgsTLefaca/bBWjvJzs6cOf/fnDkzOQJIjWm06/XKBEGgD8c6nU5VIWgBtQDPZPWtJE8O63a7LBgMMo/Hw0ql0jPjcY4RvmqXy4XMjUYDUwLtdhtmsxnYbDbI5/O0djqdFFKmsEiGZ9jP9gem0yn0ej2Yz+fg9XpfycimAD7DttstQTDKfr8Po9GIIg6Hw1Cr1RTgB+A72GAwgMPhQLBMJgNSXsFqtUI2myUo18pA6QJogefsPrLBX4QdCVatViklw+EQRFGEj88P2O12pEUGATmsXq+TaLPZ0AXgMRF2vMEqlQoJTSYTpNNpApvNZliv1/+BHDaZTAi2Wq1A3Ig0xmMej7+RcZjdbodUKkWAaDQK+GHjHPnImB88JrZIJAKFQgH2+z2BOczhcMiwRCIBgUAA+NN5BP6mj2DYff35gk6nA61WCzBn2JxO5wPM7/fLz4vD0E+OECfn8xl/0Gw2KbLxeAyLxQIsFgt8p75pDSO7h/HbpUWpewCike9WLpfB7XaDy+WCYrFI/slk8i0MnRRAUt46hPMI4vE4+Hw+ec7t9/44VgWigEeby+UgFArJWjUYOqhWG6x50rpcSfR6PVUfNOgEVRlTX0HhrZBKz4MZjUYWi8VoA+lc9H/VaRZYjBKrtXR8tlwumcFgeMWRbZpA9ORQWfVm8A/FsrLaxebd5wAAAABJRU5ErkJggg=="; diff --git a/src/everything/index.ts b/src/everything/index.ts index 39d50fa..c0ab77a 100644 --- a/src/everything/index.ts +++ b/src/everything/index.ts @@ -1,42 +1,23 @@ #!/usr/bin/env node -// Parse command line arguments first -const args = process.argv.slice(2); -const scriptName = args[0] || "stdio"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { createServer } from "./everything.js"; -async function run() { - try { - // Dynamically import only the requested module to prevent all modules from initializing - switch (scriptName) { - case "stdio": - // Import and run the default server - await import("./transports/stdio.js"); - break; - case "sse": - // Import and run the SSE server - await import("./transports/sse.js"); - break; - case "streamableHttp": - // Import and run the streamable HTTP server - await import("./transports/streamableHttp.js"); - break; - default: - console.error(`-`.repeat(53)); - console.error(` Everything Server Launcher`); - console.error(` Usage: node ./index.js [stdio|sse|streamableHttp]`); - console.error(` Default transport: stdio`); - console.error(`-`.repeat(53)); - console.error(`Unknown transport: ${scriptName}`); - console.log("Available transports:"); - console.log("- stdio"); - console.log("- sse"); - console.log("- streamableHttp"); - process.exit(1); - } - } catch (error) { - console.error("Error running script:", error); - process.exit(1); - } +async function main() { + const transport = new StdioServerTransport(); + const { server, cleanup } = createServer(); + + await server.connect(transport); + + // Cleanup on exit + process.on("SIGINT", async () => { + await cleanup(); + await server.close(); + process.exit(0); + }); } -await run(); +main().catch((error) => { + console.error("Server error:", error); + process.exit(1); +}); diff --git a/src/everything/package.json b/src/everything/package.json index 745d1b1..29df070 100644 --- a/src/everything/package.json +++ b/src/everything/package.json @@ -1,16 +1,11 @@ { "name": "@modelcontextprotocol/server-everything", - "version": "2.0.0", + "version": "0.5.1", "description": "MCP server that exercises all the features of the MCP protocol", - "license": "SEE LICENSE IN LICENSE", - "mcpName": "io.github.modelcontextprotocol/server-everything", - "author": "Model Context Protocol a Series of LF Projects, LLC.", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", "homepage": "https://modelcontextprotocol.io", "bugs": "https://github.com/modelcontextprotocol/servers/issues", - "repository": { - "type": "git", - "url": "https://github.com/modelcontextprotocol/servers.git" - }, "type": "module", "bin": { "mcp-server-everything": "dist/index.js" @@ -19,30 +14,19 @@ "dist" ], "scripts": { - "build": "tsc && shx cp -r docs dist/ && shx chmod +x dist/*.js", + "build": "tsc && shx chmod +x dist/*.js", "prepare": "npm run build", - "watch": "tsc --watch", - "start:stdio": "node dist/index.js stdio", - "start:sse": "node dist/index.js sse", - "start:streamableHttp": "node dist/index.js streamableHttp", - "prettier:fix": "prettier --write .", - "prettier:check": "prettier --check .", - "test": "vitest run --coverage" + "watch": "tsc --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "cors": "^2.8.5", - "express": "^5.2.1", - "jszip": "^3.10.1", - "zod": "^4.0.0" + "@modelcontextprotocol/sdk": "0.5.0", + "express": "^4.21.1", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.23.5" }, "devDependencies": { - "@types/cors": "^2.8.19", - "@types/express": "^5.0.6", - "@vitest/coverage-v8": "^4.1.8", - "prettier": "^2.8.8", - "shx": "^0.4.0", - "typescript": "^5.6.2", - "vitest": "^4.1.8" + "@types/express": "^5.0.0", + "shx": "^0.3.4", + "typescript": "^5.6.2" } } diff --git a/src/everything/prompts/args.ts b/src/everything/prompts/args.ts deleted file mode 100644 index 7e445a4..0000000 --- a/src/everything/prompts/args.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - -/** - * Register a prompt with arguments - * - Two arguments, one required and one optional - * - Combines argument values in the returned prompt - * - * @param server - */ -export const registerArgumentsPrompt = (server: McpServer) => { - // Prompt arguments - const promptArgsSchema = { - city: z.string().describe("Name of the city"), - state: z.string().describe("Name of the state").optional(), - }; - - // Register the prompt - server.registerPrompt( - "args-prompt", - { - title: "Arguments Prompt", - description: "A prompt with two arguments, one required and one optional", - argsSchema: promptArgsSchema, - }, - (args) => { - const location = `${args?.city}${args?.state ? `, ${args?.state}` : ""}`; - return { - messages: [ - { - role: "user", - content: { - type: "text", - text: `What's weather in ${location}?`, - }, - }, - ], - }; - } - ); -}; diff --git a/src/everything/prompts/completions.ts b/src/everything/prompts/completions.ts deleted file mode 100644 index e47c36e..0000000 --- a/src/everything/prompts/completions.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { completable } from "@modelcontextprotocol/sdk/server/completable.js"; - -/** - * Register a prompt with completable arguments - * - Two required arguments, both with completion handlers - * - First argument value will be included in context for second argument - * - Allows second argument to depend on the first argument value - * - * @param server - */ -export const registerPromptWithCompletions = (server: McpServer) => { - // Prompt arguments - const promptArgsSchema = { - department: completable( - z.string().describe("Choose the department."), - (value) => { - return ["Engineering", "Sales", "Marketing", "Support"].filter((d) => - d.startsWith(value) - ); - } - ), - name: completable( - z - .string() - .describe("Choose a team member to lead the selected department."), - (value, context) => { - const department = context?.arguments?.["department"]; - if (department === "Engineering") { - return ["Alice", "Bob", "Charlie"].filter((n) => n.startsWith(value)); - } else if (department === "Sales") { - return ["David", "Eve", "Frank"].filter((n) => n.startsWith(value)); - } else if (department === "Marketing") { - return ["Grace", "Henry", "Iris"].filter((n) => n.startsWith(value)); - } else if (department === "Support") { - return ["John", "Kim", "Lee"].filter((n) => n.startsWith(value)); - } - return []; - } - ), - }; - - // Register the prompt - server.registerPrompt( - "completable-prompt", - { - title: "Team Management", - description: "First argument choice narrows values for second argument.", - argsSchema: promptArgsSchema, - }, - ({ department, name }) => ({ - messages: [ - { - role: "user", - content: { - type: "text", - text: `Please promote ${name} to the head of the ${department} team.`, - }, - }, - ], - }) - ); -}; diff --git a/src/everything/prompts/index.ts b/src/everything/prompts/index.ts deleted file mode 100644 index 6efa7b7..0000000 --- a/src/everything/prompts/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerSimplePrompt } from "./simple.js"; -import { registerArgumentsPrompt } from "./args.js"; -import { registerPromptWithCompletions } from "./completions.js"; -import { registerEmbeddedResourcePrompt } from "./resource.js"; - -/** - * Register the prompts with the MCP server. - * - * @param server - */ -export const registerPrompts = (server: McpServer) => { - registerSimplePrompt(server); - registerArgumentsPrompt(server); - registerPromptWithCompletions(server); - registerEmbeddedResourcePrompt(server); -}; diff --git a/src/everything/prompts/resource.ts b/src/everything/prompts/resource.ts deleted file mode 100644 index 03989aa..0000000 --- a/src/everything/prompts/resource.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - resourceTypeCompleter, - resourceIdForPromptCompleter, -} from "../resources/templates.js"; -import { - textResource, - textResourceUri, - blobResourceUri, - blobResource, - RESOURCE_TYPE_BLOB, - RESOURCE_TYPE_TEXT, - RESOURCE_TYPES, -} from "../resources/templates.js"; - -/** - * Register a prompt with an embedded resource reference - * - Takes a resource type and id - * - Returns the corresponding dynamically created resource - * - * @param server - */ -export const registerEmbeddedResourcePrompt = (server: McpServer) => { - // Prompt arguments - const promptArgsSchema = { - resourceType: resourceTypeCompleter, - resourceId: resourceIdForPromptCompleter, - }; - - // Register the prompt - server.registerPrompt( - "resource-prompt", - { - title: "Resource Prompt", - description: "A prompt that includes an embedded resource reference", - argsSchema: promptArgsSchema, - }, - (args) => { - // Validate resource type argument - const resourceType = args.resourceType; - if ( - !RESOURCE_TYPES.includes( - resourceType as typeof RESOURCE_TYPE_TEXT | typeof RESOURCE_TYPE_BLOB - ) - ) { - throw new Error( - `Invalid resourceType: ${args?.resourceType}. Must be ${RESOURCE_TYPE_TEXT} or ${RESOURCE_TYPE_BLOB}.` - ); - } - - // Validate resourceId argument - const resourceId = Number(args?.resourceId); - if ( - !Number.isFinite(resourceId) || - !Number.isInteger(resourceId) || - resourceId < 1 - ) { - throw new Error( - `Invalid resourceId: ${args?.resourceId}. Must be a finite positive integer.` - ); - } - - // Get resource based on the resource type - const uri = - resourceType === RESOURCE_TYPE_TEXT - ? textResourceUri(resourceId) - : blobResourceUri(resourceId); - const resource = - resourceType === RESOURCE_TYPE_TEXT - ? textResource(uri, resourceId) - : blobResource(uri, resourceId); - - return { - messages: [ - { - role: "user", - content: { - type: "text", - text: `This prompt includes the ${resourceType} resource with id: ${resourceId}. Please analyze the following resource:`, - }, - }, - { - role: "user", - content: { - type: "resource", - resource: resource, - }, - }, - ], - }; - } - ); -}; diff --git a/src/everything/prompts/simple.ts b/src/everything/prompts/simple.ts deleted file mode 100644 index a2a0d2e..0000000 --- a/src/everything/prompts/simple.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - -/** - * Register a simple prompt with no arguments - * - Returns the fixed text of the prompt with no modifications - * - * @param server - */ -export const registerSimplePrompt = (server: McpServer) => { - // Register the prompt - server.registerPrompt( - "simple-prompt", - { - title: "Simple Prompt", - description: "A prompt with no arguments", - }, - () => ({ - messages: [ - { - role: "user", - content: { - type: "text", - text: "This is a simple prompt without arguments.", - }, - }, - ], - }) - ); -}; diff --git a/src/everything/resources/files.ts b/src/everything/resources/files.ts deleted file mode 100644 index e38cb59..0000000 --- a/src/everything/resources/files.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { dirname, join } from "path"; -import { fileURLToPath } from "url"; -import { readdirSync, readFileSync, statSync } from "fs"; - -/** - * Register static file resources - * - Each file in src/everything/docs is exposed as an individual static resource - * - URIs follow the pattern: "demo://static/docs/" - * - Markdown (.md) files are served as mime type "text/markdown" - * - Text (.txt) files are served as mime type "text/plain" - * - JSON (.json) files are served as mime type "application/json" - * - * @param server - */ -export const registerFileResources = (server: McpServer) => { - // Read the entries in the docs directory - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const docsDir = join(__dirname, "..", "docs"); - let entries: string[] = []; - try { - entries = readdirSync(docsDir); - } catch (e) { - // If docs/ folder is missing or unreadable, just skip registration - return; - } - - // Register each file as a static resource - for (const name of entries) { - // Only process files, not directories - const fullPath = join(docsDir, name); - try { - const st = statSync(fullPath); - if (!st.isFile()) continue; - } catch { - continue; - } - - // Prepare file resource info - const uri = `demo://resource/static/document/${encodeURIComponent(name)}`; - const mimeType = getMimeType(name); - const description = `Static document file exposed from /docs: ${name}`; - - // Register file resource - server.registerResource( - name, - uri, - { mimeType, description }, - async (uri) => { - const text = readFileSafe(fullPath); - return { - contents: [ - { - uri: uri.toString(), - mimeType, - text, - }, - ], - }; - } - ); - } -}; - -/** - * Get the mimetype based on filename - * @param fileName - */ -function getMimeType(fileName: string): string { - const lower = fileName.toLowerCase(); - if (lower.endsWith(".md") || lower.endsWith(".markdown")) - return "text/markdown"; - if (lower.endsWith(".txt")) return "text/plain"; - if (lower.endsWith(".json")) return "application/json"; - return "text/plain"; -} - -/** - * Read a file or return an error message if it fails - * @param path - */ -function readFileSafe(path: string): string { - try { - return readFileSync(path, "utf-8"); - } catch (e) { - return `Error reading file: ${path}. ${e}`; - } -} diff --git a/src/everything/resources/index.ts b/src/everything/resources/index.ts deleted file mode 100644 index 30c6f7d..0000000 --- a/src/everything/resources/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerResourceTemplates } from "./templates.js"; -import { registerFileResources } from "./files.js"; -import { fileURLToPath } from "url"; -import { dirname, join } from "path"; -import { readFileSync } from "fs"; - -/** - * Register the resources with the MCP server. - * @param server - */ -export const registerResources = (server: McpServer) => { - registerResourceTemplates(server); - registerFileResources(server); -}; - -/** - * Reads the server instructions from the corresponding markdown file. - * Attempts to load the content of the file located in the `docs` directory. - * If the file cannot be loaded, an error message is returned instead. - * - * @return {string} The content of the server instructions file, or an error message if reading fails. - */ -export function readInstructions(): string { - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const filePath = join(__dirname, "..", "docs", "instructions.md"); - let instructions; - - try { - instructions = readFileSync(filePath, "utf-8"); - } catch (e) { - instructions = "Server instructions not loaded: " + e; - } - return instructions; -} diff --git a/src/everything/resources/session.ts b/src/everything/resources/session.ts deleted file mode 100644 index 10e0db3..0000000 --- a/src/everything/resources/session.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { McpServer, RegisteredResource } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { Resource, ResourceLink } from "@modelcontextprotocol/sdk/types.js"; - -/** - * Tracks registered session resources by URI to allow updating/removing on re-registration. - * This prevents "Resource already registered" errors when a tool creates a resource - * with the same URI multiple times during a session. - */ -const registeredResources = new Map(); - -/** - * Generates a session-scoped resource URI string based on the provided resource name. - * - * @param {string} name - The name of the resource to create a URI for. - * @returns {string} The formatted session resource URI. - */ -export const getSessionResourceURI = (name: string): string => { - return `demo://resource/session/${name}`; -}; - -/** - * Registers a session-scoped resource with the provided server and returns a resource link. - * - * The registered resource is available during the life of the session only; it is not otherwise persisted. - * - * @param {McpServer} server - The server instance responsible for handling the resource registration. - * @param {Resource} resource - The resource object containing metadata such as URI, name, description, and mimeType. - * @param {"text"|"blob"} type - * @param payload - * @returns {ResourceLink} An object representing the resource link, with associated metadata. - */ -export const registerSessionResource = ( - server: McpServer, - resource: Resource, - type: "text" | "blob", - payload: string -): ResourceLink => { - // Destructure resource - const { uri, name, mimeType, description, title, annotations, icons, _meta } = - resource; - - // Prepare the resource content to return - // See https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource-contents - const resourceContent = - type === "text" - ? { - uri: uri.toString(), - mimeType, - text: payload, - } - : { - uri: uri.toString(), - mimeType, - blob: payload, - }; - - // Check if a resource with this URI is already registered and remove it - const existingResource = registeredResources.get(uri); - if (existingResource) { - existingResource.remove(); - registeredResources.delete(uri); - } - - // Register file resource - const registeredResource = server.registerResource( - name, - uri, - { mimeType, description, title, annotations, icons, _meta }, - async () => { - return { - contents: [resourceContent], - }; - } - ); - - // Track the registered resource for potential future removal - registeredResources.set(uri, registeredResource); - - return { type: "resource_link", ...resource }; -}; diff --git a/src/everything/resources/subscriptions.ts b/src/everything/resources/subscriptions.ts deleted file mode 100644 index 854a863..0000000 --- a/src/everything/resources/subscriptions.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - SubscribeRequestSchema, - UnsubscribeRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; - -// Track subscriber session id lists by URI -const subscriptions: Map> = new Map< - string, - Set ->(); - -// Interval to send notifications to subscribers -const subsUpdateIntervals: Map = - new Map(); - -/** - * Sets up the subscription and unsubscription handlers for the provided server. - * - * The function defines two request handlers: - * 1. A `Subscribe` handler that allows clients to subscribe to specific resource URIs. - * 2. An `Unsubscribe` handler that allows clients to unsubscribe from specific resource URIs. - * - * The `Subscribe` handler performs the following actions: - * - Extracts the URI and session ID from the request. - * - Logs a message acknowledging the subscription request. - * - Updates the internal tracking of subscribers for the given URI. - * - * The `Unsubscribe` handler performs the following actions: - * - Extracts the URI and session ID from the request. - * - Logs a message acknowledging the unsubscription request. - * - Removes the subscriber for the specified URI. - * - * @param {McpServer} server - The server instance to which subscription handlers will be attached. - */ -export const setSubscriptionHandlers = (server: McpServer) => { - // Set the subscription handler - server.server.setRequestHandler( - SubscribeRequestSchema, - async (request, extra) => { - // Get the URI to subscribe to - const { uri } = request.params; - - // Get the session id (can be undefined for stdio) - const sessionId = extra.sessionId as string; - - // Acknowledge the subscribe request - await server.sendLoggingMessage( - { - level: "info", - data: `Received Subscribe Resource request for URI: ${uri} ${ - sessionId ? `from session ${sessionId}` : "" - }`, - }, - sessionId - ); - - // Get the subscribers for this URI - const subscribers = subscriptions.has(uri) - ? (subscriptions.get(uri) as Set) - : new Set(); - subscribers.add(sessionId); - subscriptions.set(uri, subscribers); - return {}; - } - ); - - // Set the unsubscription handler - server.server.setRequestHandler( - UnsubscribeRequestSchema, - async (request, extra) => { - // Get the URI to subscribe to - const { uri } = request.params; - - // Get the session id (can be undefined for stdio) - const sessionId = extra.sessionId as string; - - // Acknowledge the subscribe request - await server.sendLoggingMessage( - { - level: "info", - data: `Received Unsubscribe Resource request: ${uri} ${ - sessionId ? `from session ${sessionId}` : "" - }`, - }, - sessionId - ); - - // Remove the subscriber - if (subscriptions.has(uri)) { - const subscribers = subscriptions.get(uri) as Set; - if (subscribers.has(sessionId)) subscribers.delete(sessionId); - } - return {}; - } - ); -}; - -/** - * Sends simulated resource update notifications to the subscribed client. - * - * Iterates the URIs in `subscriptions` and emits a - * `notifications/resources/updated` notification for each URI the given - * sessionId is subscribed to. - * - * @param {McpServer} server - The server instance used to send notifications. - * @param {string | undefined} sessionId - The session ID of the client to check for subscriptions. - * @returns {Promise} Resolves once all applicable notifications are sent. - */ -const sendSimulatedResourceUpdates = async ( - server: McpServer, - sessionId: string | undefined -): Promise => { - // Search all URIs for ones this client is subscribed to - for (const uri of subscriptions.keys()) { - const subscribers = subscriptions.get(uri) as Set; - - // If this client is subscribed, send the notification - if (subscribers.has(sessionId)) { - await server.server.notification({ - method: "notifications/resources/updated", - params: { uri }, - }); - } - } -}; - -/** - * Starts the process of simulating resource updates and sending server notifications - * to the client for the resources they are subscribed to. If the update interval is - * already active, invoking this function will not start another interval. - * - * @param server - * @param sessionId - */ -export const beginSimulatedResourceUpdates = ( - server: McpServer, - sessionId: string | undefined -) => { - if (!subsUpdateIntervals.has(sessionId)) { - // Send once immediately - sendSimulatedResourceUpdates(server, sessionId); - - // Set the interval to send later resource update notifications to this client - subsUpdateIntervals.set( - sessionId, - setInterval(() => sendSimulatedResourceUpdates(server, sessionId), 5000) - ); - } -}; - -/** - * Stops simulated resource updates for a given session. - * - * This function halts any active intervals associated with the provided session ID - * and removes the session's corresponding entries from resource management collections. - * Session ID can be undefined for stdio. - * - * @param {string} [sessionId] - */ -export const stopSimulatedResourceUpdates = (sessionId?: string) => { - // Remove active intervals - if (subsUpdateIntervals.has(sessionId)) { - const subsUpdateInterval = subsUpdateIntervals.get(sessionId); - clearInterval(subsUpdateInterval); - subsUpdateIntervals.delete(sessionId); - } -}; diff --git a/src/everything/resources/templates.ts b/src/everything/resources/templates.ts deleted file mode 100644 index 4530741..0000000 --- a/src/everything/resources/templates.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { z } from "zod"; -import { - CompleteResourceTemplateCallback, - McpServer, - ResourceTemplate, -} from "@modelcontextprotocol/sdk/server/mcp.js"; -import { completable } from "@modelcontextprotocol/sdk/server/completable.js"; - -// Resource types -export const RESOURCE_TYPE_TEXT = "Text" as const; -export const RESOURCE_TYPE_BLOB = "Blob" as const; -export const RESOURCE_TYPES: string[] = [ - RESOURCE_TYPE_TEXT, - RESOURCE_TYPE_BLOB, -]; - -/** - * A completer function for resource types. - * - * This variable provides functionality to perform autocompletion for the resource types based on user input. - * It uses a schema description to validate the input and filters through a predefined list of resource types - * to return suggestions that start with the given input. - * - * The input value is expected to be a string representing the type of resource to fetch. - * The completion logic matches the input against available resource types. - */ -export const resourceTypeCompleter = completable( - z.string().describe("Type of resource to fetch"), - (value: string) => { - return RESOURCE_TYPES.filter((t) => t.startsWith(value)); - } -); - -/** - * A completer function for resource IDs as strings. - * - * The `resourceIdCompleter` accepts a string input representing the ID of a text resource - * and validates whether the provided value corresponds to an integer resource ID. - * - * NOTE: Currently, prompt arguments can only be strings since type is not field of `PromptArgument` - * Consequently, we must define it as a string and convert the argument to number before using it - * https://modelcontextprotocol.io/specification/2025-11-25/schema#promptargument - * - * If the value is a valid integer, it returns the value within an array. - * Otherwise, it returns an empty array. - * - * The input string is first transformed into a number and checked to ensure it is an integer. - * This helps validate and suggest appropriate resource IDs. - */ -export const resourceIdForPromptCompleter = completable( - z.string().describe("ID of the text resource to fetch"), - (value: string) => { - const resourceId = Number(value); - return Number.isInteger(resourceId) && resourceId > 0 ? [value] : []; - } -); - -/** - * A callback function that acts as a completer for resource ID values, validating and returning - * the input value as part of a resource template. - * - * @typedef {CompleteResourceTemplateCallback} - * @param {string} value - The input string value to be evaluated as a resource ID. - * @returns {string[]} Returns an array containing the input value if it represents a positive - * integer resource ID, otherwise returns an empty array. - */ -export const resourceIdForResourceTemplateCompleter: CompleteResourceTemplateCallback = - (value: string) => { - const resourceId = Number(value); - - return Number.isInteger(resourceId) && resourceId > 0 ? [value] : []; - }; - -const uriBase: string = "demo://resource/dynamic"; -const textUriBase: string = `${uriBase}/text`; -const blobUriBase: string = `${uriBase}/blob`; -const textUriTemplate: string = `${textUriBase}/{resourceId}`; -const blobUriTemplate: string = `${blobUriBase}/{resourceId}`; - -/** - * Create a dynamic text resource - * - Exposed for use by embedded resource prompt example - * @param uri - * @param resourceId - */ -export const textResource = (uri: URL, resourceId: number) => { - const timestamp = new Date().toLocaleTimeString(); - return { - uri: uri.toString(), - mimeType: "text/plain", - text: `Resource ${resourceId}: This is a plaintext resource created at ${timestamp}`, - }; -}; - -/** - * Create a dynamic blob resource - * - Exposed for use by embedded resource prompt example - * @param uri - * @param resourceId - */ -export const blobResource = (uri: URL, resourceId: number) => { - const timestamp = new Date().toLocaleTimeString(); - const resourceText = Buffer.from( - `Resource ${resourceId}: This is a base64 blob created at ${timestamp}` - ).toString("base64"); - return { - uri: uri.toString(), - mimeType: "text/plain", - blob: resourceText, - }; -}; - -/** - * Create a dynamic text resource URI - * - Exposed for use by embedded resource prompt example - * @param resourceId - */ -export const textResourceUri = (resourceId: number) => - new URL(`${textUriBase}/${resourceId}`); - -/** - * Create a dynamic blob resource URI - * - Exposed for use by embedded resource prompt example - * @param resourceId - */ -export const blobResourceUri = (resourceId: number) => - new URL(`${blobUriBase}/${resourceId}`); - -/** - * Parses the resource identifier from the provided variables and validates - * that it is a positive integer. - * - * The SDK only routes URIs that match the registered template to the - * resource handler, so by the time we get here the URI prefix is already - * known to be one of `textUriBase` / `blobUriBase`. Only the resourceId - * variable still needs validating. - * - * @param {URL} uri - The URI of the resource (used in the error message). - * @param {Record} variables - Context variables including resourceId. - * @returns {number} The parsed and validated resource identifier as a positive integer. - * @throws {Error} If the resourceId is not a finite positive integer. - */ -const parseResourceId = (uri: URL, variables: Record) => { - const idxStr = String((variables as any).resourceId ?? ""); - const idx = Number(idxStr); - if (Number.isFinite(idx) && Number.isInteger(idx) && idx > 0) { - return idx; - } - throw new Error(`Unknown resource: ${uri.toString()}`); -}; - -/** - * Register resource templates with the MCP server. - * - Text and blob resources, dynamically generated from the URI {resourceId} variable - * - Any finite positive integer is acceptable for the resourceId variable - * - List resources method will not return these resources - * - These are only accessible via template URIs - * - Both blob and text resources: - * - have content that is dynamically generated, including a timestamp - * - have different template URIs - * - Blob: "demo://resource/dynamic/blob/{resourceId}" - * - Text: "demo://resource/dynamic/text/{resourceId}" - * - * @param server - */ -export const registerResourceTemplates = (server: McpServer) => { - // Register the text resource template - server.registerResource( - "Dynamic Text Resource", - new ResourceTemplate(textUriTemplate, { - list: undefined, - complete: { resourceId: resourceIdForResourceTemplateCompleter }, - }), - { - mimeType: "text/plain", - description: - "Plaintext dynamic resource fabricated from the {resourceId} variable, which must be an integer.", - }, - async (uri, variables) => { - const resourceId = parseResourceId(uri, variables); - return { - contents: [textResource(uri, resourceId)], - }; - } - ); - - // Register the blob resource template - server.registerResource( - "Dynamic Blob Resource", - new ResourceTemplate(blobUriTemplate, { - list: undefined, - complete: { resourceId: resourceIdForResourceTemplateCompleter }, - }), - { - mimeType: "application/octet-stream", - description: - "Binary (base64) dynamic resource fabricated from the {resourceId} variable, which must be an integer.", - }, - async (uri, variables) => { - const resourceId = parseResourceId(uri, variables); - return { - contents: [blobResource(uri, resourceId)], - }; - } - ); -}; diff --git a/src/everything/server/index.ts b/src/everything/server/index.ts deleted file mode 100644 index f1459cc..0000000 --- a/src/everything/server/index.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - InMemoryTaskStore, - InMemoryTaskMessageQueue, -} from "@modelcontextprotocol/sdk/experimental/tasks"; -import { - setSubscriptionHandlers, - stopSimulatedResourceUpdates, -} from "../resources/subscriptions.js"; -import { registerConditionalTools, registerTools } from "../tools/index.js"; -import { registerResources, readInstructions } from "../resources/index.js"; -import { registerPrompts } from "../prompts/index.js"; -import { stopSimulatedLogging } from "./logging.js"; -import { syncRoots } from "./roots.js"; - -// Server Factory response -export type ServerFactoryResponse = { - server: McpServer; - cleanup: (sessionId?: string) => void; -}; - -/** - * Server Factory - * - * This function initializes a `McpServer` with specific capabilities and instructions, - * registers tools, resources, and prompts, and configures resource subscription handlers. - * - * @returns {ServerFactoryResponse} An object containing the server instance, and a `cleanup` - * function for handling server-side cleanup when a session ends. - * - * Properties of the returned object: - * - `server` {Object}: The initialized server instance. - * - `cleanup` {Function}: Function to perform cleanup operations for a closing session. - */ -export const createServer: () => ServerFactoryResponse = () => { - // Read the server instructions - const instructions = readInstructions(); - - // Create task store and message queue for task support - const taskStore = new InMemoryTaskStore(); - const taskMessageQueue = new InMemoryTaskMessageQueue(); - - let initializeTimeout: NodeJS.Timeout | null = null; - - // Create the server - const server = new McpServer( - { - name: "mcp-servers/everything", - title: "Everything Reference Server", - version: "2.0.0", - }, - { - capabilities: { - tools: { - listChanged: true, - }, - prompts: { - listChanged: true, - }, - resources: { - subscribe: true, - listChanged: true, - }, - logging: {}, - tasks: { - list: {}, - cancel: {}, - requests: { - tools: { - call: {}, - }, - }, - }, - }, - instructions, - taskStore, - taskMessageQueue, - } - ); - - // Register the tools - registerTools(server); - - // Register the resources - registerResources(server); - - // Register the prompts - registerPrompts(server); - - // Set resource subscription handlers - setSubscriptionHandlers(server); - - // Perform post-initialization operations - server.server.oninitialized = async () => { - // Register conditional tools now that client capabilities are known. - // This finishes before the `notifications/initialized` handler finishes. - registerConditionalTools(server); - - // Sync roots if the client supports them. - // This is delayed until after the `notifications/initialized` handler finishes, - // otherwise, the request gets lost. - const sessionId = server.server.transport?.sessionId; - initializeTimeout = setTimeout(() => syncRoots(server, sessionId), 350); - }; - - // Return the ServerFactoryResponse - return { - server, - cleanup: (sessionId?: string) => { - // Stop any simulated logging or resource updates that may have been initiated. - stopSimulatedLogging(sessionId); - stopSimulatedResourceUpdates(sessionId); - // Clean up task store timers - taskStore.cleanup(); - if (initializeTimeout) clearTimeout(initializeTimeout); - }, - } satisfies ServerFactoryResponse; -}; diff --git a/src/everything/server/logging.ts b/src/everything/server/logging.ts deleted file mode 100644 index 82edea1..0000000 --- a/src/everything/server/logging.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { LoggingLevel } from "@modelcontextprotocol/sdk/types.js"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - -// Map session ID to the interval for sending logging messages to the client -const logsUpdateIntervals: Map = - new Map(); - -/** - * Initiates a simulated logging process by sending random log messages to the client at a - * fixed interval. Each log message contains a random logging level and optional session ID. - * - * @param {McpServer} server - The server instance responsible for handling the logging messages. - * @param {string | undefined} sessionId - An optional identifier for the session. If provided, - * the session ID will be appended to log messages. - */ -export const beginSimulatedLogging = ( - server: McpServer, - sessionId: string | undefined -) => { - const maybeAppendSessionId = sessionId ? ` - SessionId ${sessionId}` : ""; - const messages: { level: LoggingLevel; data: string }[] = [ - { level: "debug", data: `Debug-level message${maybeAppendSessionId}` }, - { level: "info", data: `Info-level message${maybeAppendSessionId}` }, - { level: "notice", data: `Notice-level message${maybeAppendSessionId}` }, - { - level: "warning", - data: `Warning-level message${maybeAppendSessionId}`, - }, - { level: "error", data: `Error-level message${maybeAppendSessionId}` }, - { - level: "critical", - data: `Critical-level message${maybeAppendSessionId}`, - }, - { level: "alert", data: `Alert level-message${maybeAppendSessionId}` }, - { - level: "emergency", - data: `Emergency-level message${maybeAppendSessionId}`, - }, - ]; - - /** - * Send a simulated logging message to the client - */ - const sendSimulatedLoggingMessage = async (sessionId: string | undefined) => { - // By using the `sendLoggingMessage` function to send the message, we - // ensure that the client's chosen logging level will be respected - await server.sendLoggingMessage( - messages[Math.floor(Math.random() * messages.length)], - sessionId - ); - }; - - // Set the interval to send later logging messages to this client - if (!logsUpdateIntervals.has(sessionId)) { - // Send once immediately - sendSimulatedLoggingMessage(sessionId); - - // Send a randomly-leveled log message every 5 seconds - logsUpdateIntervals.set( - sessionId, - setInterval(() => sendSimulatedLoggingMessage(sessionId), 5000) - ); - } -}; - -/** - * Stops the simulated logging process for a given session. - * - * This function halts the periodic logging updates associated with the specified - * session ID by clearing the interval and removing the session's tracking - * reference. Session ID can be undefined for stdio. - * - * @param {string} [sessionId] - The optional unique identifier of the session. - */ -export const stopSimulatedLogging = (sessionId?: string) => { - // Remove active intervals - if (logsUpdateIntervals.has(sessionId)) { - const logsUpdateInterval = logsUpdateIntervals.get(sessionId); - clearInterval(logsUpdateInterval); - logsUpdateIntervals.delete(sessionId); - } -}; diff --git a/src/everything/server/roots.ts b/src/everything/server/roots.ts deleted file mode 100644 index 34b12b2..0000000 --- a/src/everything/server/roots.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - Root, - RootsListChangedNotificationSchema, -} from "@modelcontextprotocol/sdk/types.js"; - -// Track roots by session id -export const roots: Map = new Map< - string | undefined, - Root[] ->(); - -/** - * Get the latest the client roots list for the session. - * - * - Request and cache the roots list for the session if it has not been fetched before. - * - Return the cached roots list for the session if it exists. - * - * When requesting the roots list for a session, it also sets up a `roots/list_changed` - * notification handler. This ensures that updates are automatically fetched and handled - * in real-time. - * - * This function is idempotent. It should only request roots from the client once per session, - * returning the cached version thereafter. - * - * @param {McpServer} server - An instance of the MCP server used to communicate with the client. - * @param {string} [sessionId] - An optional session id used to associate the roots list with a specific client session. - * - * @throws {Error} In case of a failure to request the roots from the client, an error log message is sent. - */ -export const syncRoots = async (server: McpServer, sessionId?: string) => { - const clientCapabilities = server.server.getClientCapabilities() || {}; - const clientSupportsRoots: boolean = clientCapabilities?.roots !== undefined; - - // Fetch the roots list for this client - if (clientSupportsRoots) { - // Function to request the updated roots list from the client - const requestRoots = async () => { - try { - // Request the updated roots list from the client - const response = await server.server.listRoots(); - if (response && "roots" in response) { - // Store the roots list for this client - roots.set(sessionId, response.roots); - - // Notify the client of roots received - await server.sendLoggingMessage( - { - level: "info", - logger: "everything-server", - data: `Roots updated: ${response?.roots?.length} root(s) received from client`, - }, - sessionId - ); - } else { - await server.sendLoggingMessage( - { - level: "info", - logger: "everything-server", - data: "Client returned no roots set", - }, - sessionId - ); - } - } catch (error) { - console.error( - `Failed to request roots from client ${sessionId}: ${ - error instanceof Error ? error.message : String(error) - }` - ); - } - }; - - // If the roots have not been synced for this client, - // set notification handler and request initial roots - if (!roots.has(sessionId)) { - // Set the list changed notification handler - server.server.setNotificationHandler( - RootsListChangedNotificationSchema, - requestRoots - ); - - // Request the initial roots list immediately - await requestRoots(); - } - - // Return the roots list for this client - return roots.get(sessionId); - } -}; diff --git a/src/everything/sse.ts b/src/everything/sse.ts new file mode 100644 index 0000000..7a02eb5 --- /dev/null +++ b/src/everything/sse.ts @@ -0,0 +1,32 @@ +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import express from "express"; +import { createServer } from "./everything.js"; + +const app = express(); + +const { server, cleanup } = createServer(); + +let transport: SSEServerTransport; + +app.get("/sse", async (req, res) => { + console.log("Received connection"); + transport = new SSEServerTransport("/message", res); + await server.connect(transport); + + server.onclose = async () => { + await cleanup(); + await server.close(); + process.exit(0); + }; +}); + +app.post("/message", async (req, res) => { + console.log("Received message"); + + await transport.handlePostMessage(req, res); +}); + +const PORT = process.env.PORT || 3001; +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); diff --git a/src/everything/tools/echo.ts b/src/everything/tools/echo.ts deleted file mode 100644 index 0658e83..0000000 --- a/src/everything/tools/echo.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; - -// Tool input schema -export const EchoSchema = z.object({ - message: z.string().describe("Message to echo"), -}); - -// Tool configuration -const name = "echo"; -const config = { - title: "Echo Tool", - description: "Echoes back the input string", - inputSchema: EchoSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, -}; - -/** - * Registers the 'echo' tool. - * - * The registered tool validates input arguments using the EchoSchema and - * returns a response that echoes the message provided in the arguments. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - * @returns {void} - */ -export const registerEchoTool = (server: McpServer) => { - server.registerTool(name, config, async (args): Promise => { - const validatedArgs = EchoSchema.parse(args); - return { - content: [{ type: "text", text: `Echo: ${validatedArgs.message}` }], - }; - }); -}; diff --git a/src/everything/tools/get-annotated-message.ts b/src/everything/tools/get-annotated-message.ts deleted file mode 100644 index 5de72b0..0000000 --- a/src/everything/tools/get-annotated-message.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; -import { MCP_TINY_IMAGE } from "./get-tiny-image.js"; - -// Tool input schema -const GetAnnotatedMessageSchema = z.object({ - messageType: z - .enum(["error", "success", "debug"]) - .describe("Type of message to demonstrate different annotation patterns"), - includeImage: z - .boolean() - .default(false) - .describe("Whether to include an example image"), -}); - -// Tool configuration -const name = "get-annotated-message"; -const config = { - title: "Get Annotated Message Tool", - description: - "Demonstrates how annotations can be used to provide metadata about content.", - inputSchema: GetAnnotatedMessageSchema, - annotations: { - readOnlyHint: true, // This tool only returns data, no side effects - destructiveHint: false, // Does not delete or modify anything - idempotentHint: true, // Same input always produces same output - openWorldHint: false, // Does not interact with external systems - }, -}; - -/** - * Registers the 'get-annotated-message' tool. - * - * The registered tool generates and sends messages with specific types, such as error, - * success, or debug, carrying associated annotations like priority level and intended - * audience. - * - * The response will have annotations and optionally contain an annotated image. - * - * @function - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerGetAnnotatedMessageTool = (server: McpServer) => { - server.registerTool(name, config, async (args): Promise => { - const { messageType, includeImage } = GetAnnotatedMessageSchema.parse(args); - - const content: CallToolResult["content"] = []; - - // Main message with different priorities/audiences based on type - if (messageType === "error") { - content.push({ - type: "text", - text: "Error: Operation failed", - annotations: { - priority: 1.0, // Errors are highest priority - audience: ["user", "assistant"], // Both need to know about errors - }, - }); - } else if (messageType === "success") { - content.push({ - type: "text", - text: "Operation completed successfully", - annotations: { - priority: 0.7, // Success messages are important but not critical - audience: ["user"], // Success mainly for user consumption - }, - }); - } else if (messageType === "debug") { - content.push({ - type: "text", - text: "Debug: Cache hit ratio 0.95, latency 150ms", - annotations: { - priority: 0.3, // Debug info is low priority - audience: ["assistant"], // Technical details for assistant - }, - }); - } - - // Optional image with its own annotations - if (includeImage) { - content.push({ - type: "image", - data: MCP_TINY_IMAGE, - mimeType: "image/png", - annotations: { - priority: 0.5, - audience: ["user"], // Images primarily for user visualization - }, - }); - } - - return { content }; - }); -}; diff --git a/src/everything/tools/get-env.ts b/src/everything/tools/get-env.ts deleted file mode 100644 index 55eabfa..0000000 --- a/src/everything/tools/get-env.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; - -// Tool configuration -const name = "get-env"; -const config = { - title: "Print Environment Tool", - description: - "Returns all environment variables, helpful for debugging MCP server configuration", - inputSchema: {}, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, -}; - -/** - * Registers the 'get-env' tool. - * - * The registered tool Retrieves and returns the environment variables - * of the current process as a JSON-formatted string encapsulated in a text response. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - * @returns {void} - */ -export const registerGetEnvTool = (server: McpServer) => { - server.registerTool(name, config, async (args): Promise => { - return { - content: [ - { - type: "text", - text: JSON.stringify(process.env, null, 2), - }, - ], - }; - }); -}; diff --git a/src/everything/tools/get-resource-links.ts b/src/everything/tools/get-resource-links.ts deleted file mode 100644 index 7684cb6..0000000 --- a/src/everything/tools/get-resource-links.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { - textResource, - textResourceUri, - blobResourceUri, - blobResource, -} from "../resources/templates.js"; - -// Tool input schema -const GetResourceLinksSchema = z.object({ - count: z - .number() - .min(1) - .max(10) - .default(3) - .describe("Number of resource links to return (1-10)"), -}); - -// Tool configuration -const name = "get-resource-links"; -const config = { - title: "Get Resource Links Tool", - description: - "Returns up to ten resource links that reference different types of resources", - inputSchema: GetResourceLinksSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, -}; - -/** - * Registers the 'get-resource-reference' tool. - * - * The registered tool retrieves a specified number of resource links and their metadata. - * Resource links are dynamically generated as either text or binary blob resources, - * based on their ID being even or odd. - - * The response contains a "text" introductory block and multiple "resource_link" blocks. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerGetResourceLinksTool = (server: McpServer) => { - server.registerTool(name, config, async (args): Promise => { - const { count } = GetResourceLinksSchema.parse(args); - - // Add intro text content block - const content: CallToolResult["content"] = []; - content.push({ - type: "text", - text: `Here are ${count} resource links to resources available in this server:`, - }); - - // Create resource link content blocks - for (let resourceId = 1; resourceId <= count; resourceId++) { - // Get resource uri for text or blob resource based on odd/even resourceId - const isOdd = resourceId % 2 === 0; - const uri = isOdd - ? textResourceUri(resourceId) - : blobResourceUri(resourceId); - - // Get resource based on the resource type - const resource = isOdd - ? textResource(uri, resourceId) - : blobResource(uri, resourceId); - - content.push({ - type: "resource_link", - uri: resource.uri, - name: `${isOdd ? "Text" : "Blob"} Resource ${resourceId}`, - description: `Resource ${resourceId}: ${ - resource.mimeType === "text/plain" - ? "plaintext resource" - : "binary blob resource" - }`, - mimeType: resource.mimeType, - }); - } - - return { content }; - }); -}; diff --git a/src/everything/tools/get-resource-reference.ts b/src/everything/tools/get-resource-reference.ts deleted file mode 100644 index 5806365..0000000 --- a/src/everything/tools/get-resource-reference.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { - textResource, - textResourceUri, - blobResourceUri, - blobResource, - RESOURCE_TYPE_BLOB, - RESOURCE_TYPE_TEXT, - RESOURCE_TYPES, -} from "../resources/templates.js"; - -// Tool input schema -const GetResourceReferenceSchema = z.object({ - resourceType: z - .enum([RESOURCE_TYPE_TEXT, RESOURCE_TYPE_BLOB]) - .default(RESOURCE_TYPE_TEXT), - resourceId: z - .number() - .default(1) - .describe("ID of the text resource to fetch"), -}); - -// Tool configuration -const name = "get-resource-reference"; -const config = { - title: "Get Resource Reference Tool", - description: "Returns a resource reference that can be used by MCP clients", - inputSchema: GetResourceReferenceSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, -}; - -/** - * Registers the 'get-resource-reference' tool. - * - * The registered tool validates and processes arguments for retrieving a resource - * reference. Supported resource types include predefined `RESOURCE_TYPE_TEXT` and - * `RESOURCE_TYPE_BLOB`. The retrieved resource's reference will include the resource - * ID, type, and its associated URI. - * - * The tool performs the following operations: - * 1. Validates the `resourceType` argument to ensure it matches a supported type. - * 2. Validates the `resourceId` argument to ensure it is a finite positive integer. - * 3. Constructs a URI for the resource based on its type (text or blob). - * 4. Retrieves the resource and returns it in a content block. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerGetResourceReferenceTool = (server: McpServer) => { - server.registerTool(name, config, async (args): Promise => { - // Validate resource type argument - const { resourceType } = args; - if (!RESOURCE_TYPES.includes(resourceType)) { - throw new Error( - `Invalid resourceType: ${args?.resourceType}. Must be ${RESOURCE_TYPE_TEXT} or ${RESOURCE_TYPE_BLOB}.` - ); - } - - // Validate resourceId argument - const resourceId = Number(args?.resourceId); - if ( - !Number.isFinite(resourceId) || - !Number.isInteger(resourceId) || - resourceId < 1 - ) { - throw new Error( - `Invalid resourceId: ${args?.resourceId}. Must be a finite positive integer.` - ); - } - - // Get resource based on the resource type - const uri = - resourceType === RESOURCE_TYPE_TEXT - ? textResourceUri(resourceId) - : blobResourceUri(resourceId); - const resource = - resourceType === RESOURCE_TYPE_TEXT - ? textResource(uri, resourceId) - : blobResource(uri, resourceId); - - return { - content: [ - { - type: "text", - text: `Returning resource reference for Resource ${resourceId}:`, - }, - { - type: "resource", - resource: resource, - }, - { - type: "text", - text: `You can access this resource using the URI: ${resource.uri}`, - }, - ], - }; - }); -}; diff --git a/src/everything/tools/get-roots-list.ts b/src/everything/tools/get-roots-list.ts deleted file mode 100644 index a8778e4..0000000 --- a/src/everything/tools/get-roots-list.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { syncRoots } from "../server/roots.js"; - -// Tool configuration -const name = "get-roots-list"; -const config = { - title: "Get Roots List Tool", - description: - "Lists the current MCP roots provided by the client. Demonstrates the roots protocol capability even though this server doesn't access files.", - inputSchema: {}, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, -}; - -/** - * Registers the 'get-roots-list' tool. - * - * If the client does not support the roots capability, the tool is not registered. - * - * The registered tool interacts with the MCP roots capability, which enables the server to access - * information about the client's workspace directories or file system roots. - * - * When supported, the server automatically retrieves and formats the current list of roots from the - * client upon connection and whenever the client sends a `roots/list_changed` notification. - * - * Therefore, this tool displays the roots that the server currently knows about for the connected - * client. If for some reason the server never got the initial roots list, the tool will request the - * list from the client again. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerGetRootsListTool = (server: McpServer) => { - // Does client support roots? - const clientCapabilities = server.server.getClientCapabilities() || {}; - const clientSupportsRoots: boolean = clientCapabilities.roots !== undefined; - - // If so, register tool - if (clientSupportsRoots) { - server.registerTool( - name, - config, - async (args, extra): Promise => { - // Get the current rootsFetch the current roots list from the client if need be - const currentRoots = await syncRoots(server, extra.sessionId); - - // Respond if client supports roots but doesn't have any configured - if ( - clientSupportsRoots && - (!currentRoots || currentRoots.length === 0) - ) { - return { - content: [ - { - type: "text", - text: - "The client supports roots but no roots are currently configured.\n\n" + - "This could mean:\n" + - "1. The client hasn't provided any roots yet\n" + - "2. The client provided an empty roots list\n" + - "3. The roots configuration is still being loaded", - }, - ], - }; - } - - // Create formatted response if there is a list of roots - const rootsList = currentRoots - ? currentRoots - .map((root, index) => { - return `${index + 1}. ${root.name || "Unnamed Root"}\n URI: ${ - root.uri - }`; - }) - .join("\n\n") - : "No roots found"; - - return { - content: [ - { - type: "text", - text: - `Current MCP Roots (${ - currentRoots!.length - } total):\n\n${rootsList}\n\n` + - "Note: This server demonstrates the roots protocol capability but doesn't actually access files. " + - "The roots are provided by the MCP client and can be used by servers that need file system access.", - }, - ], - }; - } - ); - } -}; diff --git a/src/everything/tools/get-structured-content.ts b/src/everything/tools/get-structured-content.ts deleted file mode 100644 index f9bde5c..0000000 --- a/src/everything/tools/get-structured-content.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - CallToolResult, - ContentBlock, -} from "@modelcontextprotocol/sdk/types.js"; - -// Tool input schema -const GetStructuredContentInputSchema = { - location: z - .enum(["New York", "Chicago", "Los Angeles"]) - .describe("Choose city"), -}; - -// Tool output schema -const GetStructuredContentOutputSchema = z.object({ - temperature: z.number().describe("Temperature in celsius"), - conditions: z.string().describe("Weather conditions description"), - humidity: z.number().describe("Humidity percentage"), -}); - -// Tool configuration -const name = "get-structured-content"; -const config = { - title: "Get Structured Content Tool", - description: - "Returns structured content along with an output schema for client data validation", - inputSchema: GetStructuredContentInputSchema, - outputSchema: GetStructuredContentOutputSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, -}; - -/** - * Registers the 'get-structured-content' tool. - * - * The registered tool processes incoming arguments using a predefined input schema, - * generates structured content with weather information including temperature, - * conditions, and humidity, and returns both backward-compatible content blocks - * and structured content in the response. - * - * The response contains: - * - `content`: An array of content blocks, presented as JSON stringified objects. - * - `structuredContent`: A JSON structured representation of the weather data. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerGetStructuredContentTool = (server: McpServer) => { - server.registerTool(name, config, async (args): Promise => { - // Get simulated weather for the chosen city - let weather; - switch (args.location) { - case "New York": - weather = { - temperature: 33, - conditions: "Cloudy", - humidity: 82, - }; - break; - - case "Chicago": - weather = { - temperature: 36, - conditions: "Light rain / drizzle", - humidity: 82, - }; - break; - - case "Los Angeles": - weather = { - temperature: 73, - conditions: "Sunny / Clear", - humidity: 48, - }; - break; - } - - const backwardCompatibleContentBlock: ContentBlock = { - type: "text", - text: JSON.stringify(weather), - }; - - return { - content: [backwardCompatibleContentBlock], - structuredContent: weather, - }; - }); -}; diff --git a/src/everything/tools/get-sum.ts b/src/everything/tools/get-sum.ts deleted file mode 100644 index 470293b..0000000 --- a/src/everything/tools/get-sum.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; - -// Tool input schema -const GetSumSchema = z.object({ - a: z.number().describe("First number"), - b: z.number().describe("Second number"), -}); - -// Tool configuration -const name = "get-sum"; -const config = { - title: "Get Sum Tool", - description: "Returns the sum of two numbers", - inputSchema: GetSumSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, -}; - -/** - * Registers the 'get-sum' tool. - ** - * The registered tool processes input arguments, validates them using a predefined schema, - * calculates the sum of two numeric values, and returns the result in a content block. - * - * Expects input arguments to conform to a specific schema that includes two numeric properties, `a` and `b`. - * Validation is performed to ensure the input adheres to the expected structure before calculating the sum. - * - * The result is returned as a Promise resolving to an object containing the computed sum in a text format. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerGetSumTool = (server: McpServer) => { - server.registerTool(name, config, async (args): Promise => { - const validatedArgs = GetSumSchema.parse(args); - const sum = validatedArgs.a + validatedArgs.b; - return { - content: [ - { - type: "text", - text: `The sum of ${validatedArgs.a} and ${validatedArgs.b} is ${sum}.`, - }, - ], - }; - }); -}; diff --git a/src/everything/tools/get-tiny-image.ts b/src/everything/tools/get-tiny-image.ts deleted file mode 100644 index c38c3c6..0000000 --- a/src/everything/tools/get-tiny-image.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; - -// A tiny encoded MCP logo image -export const MCP_TINY_IMAGE = - "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAKsGlDQ1BJQ0MgUHJvZmlsZQAASImVlwdUU+kSgOfe9JDQEiIgJfQmSCeAlBBaAAXpYCMkAUKJMRBU7MriClZURLCs6KqIgo0idizYFsWC3QVZBNR1sWDDlXeBQ9jdd9575805c+a7c+efmf+e/z9nLgCdKZDJMlF1gCxpjjwyyI8dn5DIJvUABRiY0kBdIMyWcSMiwgCTUft3+dgGyJC9YzuU69/f/1fREImzhQBIBMbJomxhFsbHMe0TyuQ5ALg9mN9kbo5siK9gzJRjDWL8ZIhTR7hviJOHGY8fjomO5GGsDUCmCQTyVACaKeZn5wpTsTw0f4ztpSKJFGPsGbyzsmaLMMbqgiUWI8N4KD8n+S95Uv+WM1mZUyBIVfLIXoaF7C/JlmUK5v+fn+N/S1amYrSGOaa0NHlwJGaxvpAHGbNDlSxNnhI+yhLRcPwwpymCY0ZZmM1LHGWRwD9UuTZzStgop0gC+co8OfzoURZnB0SNsnx2pLJWipzHHWWBfKyuIiNG6U8T85X589Ki40Y5VxI7ZZSzM6JCx2J4Sr9cEansXywN8hurG6jce1b2X/Yr4SvX5qRFByv3LhjrXyzljuXMjlf2JhL7B4zFxCjjZTl+ylqyzAhlvDgzSOnPzo1Srs3BDuTY2gjlN0wXhESMMoRBELAhBjIhB+QggECQgBTEOeJ5Q2cUeLNl8+WS1LQcNhe7ZWI2Xyq0m8B2tHd0Bhi6syNH4j1r+C4irGtjvhWVAF4nBgcHT475Qm4BHEkCoNaO+SxnAKh3A1w5JVTIc0d8Q9cJCEAFNWCCDhiACViCLTiCK3iCLwRACIRDNCTATBBCGmRhnc+FhbAMCqAI1sNmKIOdsBv2wyE4CvVwCs7DZbgOt+AePIZ26IJX0AcfYQBBEBJCRxiIDmKImCE2iCPCQbyRACQMiUQSkCQkFZEiCmQhsgIpQoqRMmQXUokcQU4g55GrSCvyEOlAepF3yFcUh9JQJqqPmqMTUQ7KRUPRaHQGmorOQfPQfHQtWopWoAfROvQ8eh29h7ajr9B+HOBUcCycEc4Wx8HxcOG4RFwKTo5bjCvEleAqcNW4Rlwz7g6uHfca9wVPxDPwbLwt3hMfjI/BC/Fz8Ivxq/Fl+P34OvxF/B18B74P/51AJ+gRbAgeBD4hnpBKmEsoIJQQ9hJqCZcI9whdhI9EIpFFtCC6EYOJCcR04gLiauJ2Yg3xHLGV2EnsJ5FIOiQbkhcpnCQg5ZAKSFtJB0lnSbdJXaTPZBWyIdmRHEhOJEvJy8kl5APkM+Tb5G7yAEWdYkbxoIRTRJT5lHWUPZRGyk1KF2WAqkG1oHpRo6np1GXUUmo19RL1CfW9ioqKsYq7ylQVicpSlVKVwypXVDpUvtA0adY0Hm06TUFbS9tHO0d7SHtPp9PN6b70RHoOfS29kn6B/oz+WZWhaqfKVxWpLlEtV61Tva36Ro2iZqbGVZuplqdWonZM7abaa3WKurk6T12gvli9XP2E+n31fg2GhoNGuEaWxmqNAxpXNXo0SZrmmgGaIs18zd2aFzQ7GTiGCYPHEDJWMPYwLjG6mESmBZPPTGcWMQ8xW5h9WppazlqxWvO0yrVOa7WzcCxzFp+VyVrHOspqY30dpz+OO048btW46nG3x33SHq/tqy3WLtSu0b6n/VWHrROgk6GzQade56kuXtdad6ruXN0dupd0X49njvccLxxfOP7o+Ed6qJ61XqTeAr3dejf0+vUN9IP0Zfpb9S/ovzZgGfgapBtsMjhj0GvIMPQ2lBhuMjxr+JKtxeayM9ml7IvsPiM9o2AjhdEuoxajAWML4xjj5cY1xk9NqCYckxSTTSZNJn2mhqaTTReaVpk+MqOYcczSzLaYNZt9MrcwjzNfaV5v3mOhbcG3yLOosnhiSbf0sZxjWWF514poxbHKsNpudcsatXaxTrMut75pg9q42khsttu0TiBMcJ8gnVAx4b4tzZZrm2tbZdthx7ILs1tuV2/3ZqLpxMSJGyY2T/xu72Kfab/H/rGDpkOIw3KHRod3jtaOQsdyx7tOdKdApyVODU5vnW2cxc47nB+4MFwmu6x0aXL509XNVe5a7drrZuqW5LbN7T6HyYngrOZccSe4+7kvcT/l/sXD1SPH46jHH562nhmeBzx7JllMEk/aM6nTy9hL4LXLq92b7Z3k/ZN3u4+Rj8Cnwue5r4mvyHevbzfXipvOPch942fvJ/er9fvE8+At4p3zx/kH+Rf6twRoBsQElAU8CzQOTA2sCuwLcglaEHQumBAcGrwh+D5fny/kV/L7QtxCFoVcDKWFRoWWhT4Psw6ThzVORieHTN44+ckUsynSKfXhEM4P3xj+NMIiYk7EyanEqRFTy6e+iHSIXBjZHMWImhV1IOpjtF/0uujHMZYxipimWLXY6bGVsZ/i/OOK49rjJ8Yvir+eoJsgSWhIJCXGJu5N7J8WMG3ztK7pLtMLprfNsJgxb8bVmbozM2eenqU2SzDrWBIhKS7pQNI3QbigQtCfzE/eltwn5Am3CF+JfEWbRL1iL3GxuDvFK6U4pSfVK3Vjam+aT1pJ2msJT1ImeZsenL4z/VNGeMa+jMHMuMyaLHJWUtYJqaY0Q3pxtsHsebNbZTayAln7HI85m+f0yUPle7OR7BnZDTlMbDi6obBU/KDoyPXOLc/9PDd27rF5GvOk827Mt56/an53XmDezwvwC4QLmhYaLVy2sGMRd9Guxcji5MVNS0yW5C/pWhq0dP8y6rKMZb8st19evPzDirgVjfn6+UvzO38I+qGqQLVAXnB/pefKnT/if5T82LLKadXWVd8LRYXXiuyLSoq+rRauvrbGYU3pmsG1KWtb1rmu27GeuF66vm2Dz4b9xRrFecWdGydvrNvE3lS46cPmWZuvljiX7NxC3aLY0l4aVtqw1XTr+q3fytLK7pX7ldds09u2atun7aLtt3f47qjeqb+zaOfXnyQ/PdgVtKuuwryiZDdxd+7uF3ti9zT/zPm5cq/u3qK9f+6T7mvfH7n/YqVbZeUBvQPrqtAqRVXvwekHbx3yP9RQbVu9q4ZVU3QYDisOvzySdKTtaOjRpmOcY9XHzY5vq2XUFtYhdfPr+urT6tsbEhpaT4ScaGr0bKw9aXdy3ymjU+WntU6vO0M9k39m8Gze2f5zsnOvz6ee72ya1fT4QvyFuxenXmy5FHrpyuXAyxeauc1nr3hdOXXV4+qJa5xr9dddr9fdcLlR+4vLL7Utri11N91uNtzyv9XYOqn1zG2f2+fv+N+5fJd/9/q9Kfda22LaHtyffr/9gehBz8PMh28f5T4aeLz0CeFJ4VP1pyXP9J5V/Gr1a027a/vpDv+OG8+jnj/uFHa++i37t29d+S/oL0q6Dbsrexx7TvUG9t56Oe1l1yvZq4HXBb9r/L7tjeWb43/4/nGjL76v66387eC71e913u/74PyhqT+i/9nHrI8Dnwo/63ze/4Xzpflr3NfugbnfSN9K/7T6s/F76Pcng1mDgzKBXDA8CuAwRVNSAN7tA6AnADCwGYI6bWSmHhZk5D9gmOA/8cjcPSyuANWYGRqNeOcADmNqvhRAzRdgaCyK9gXUyUmpo/Pv8Kw+JAbYv8K0HECi2x6tebQU/iEjc/xf+v6nBWXWv9l/AV0EC6JTIblRAAAAeGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAJAAAAABAAAAkAAAAAEAAqACAAQAAAABAAAAFKADAAQAAAABAAAAFAAAAAAXNii1AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB82lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjE0NDwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+MTQ0PC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KReh49gAAAjRJREFUOBGFlD2vMUEUx2clvoNCcW8hCqFAo1dKhEQpvsF9KrWEBh/ALbQ0KkInBI3SWyGPCCJEQliXgsTLefaca/bBWjvJzs6cOf/fnDkzOQJIjWm06/XKBEGgD8c6nU5VIWgBtQDPZPWtJE8O63a7LBgMMo/Hw0ql0jPjcY4RvmqXy4XMjUYDUwLtdhtmsxnYbDbI5/O0djqdFFKmsEiGZ9jP9gem0yn0ej2Yz+fg9XpfycimAD7DttstQTDKfr8Po9GIIg6Hw1Cr1RTgB+A72GAwgMPhQLBMJgNSXsFqtUI2myUo18pA6QJogefsPrLBX4QdCVatViklw+EQRFGEj88P2O12pEUGATmsXq+TaLPZ0AXgMRF2vMEqlQoJTSYTpNNpApvNZliv1/+BHDaZTAi2Wq1A3Ig0xmMej7+RcZjdbodUKkWAaDQK+GHjHPnImB88JrZIJAKFQgH2+z2BOczhcMiwRCIBgUAA+NN5BP6mj2DYff35gk6nA61WCzBn2JxO5wPM7/fLz4vD0E+OECfn8xl/0Gw2KbLxeAyLxQIsFgt8p75pDSO7h/HbpUWpewCike9WLpfB7XaDy+WCYrFI/slk8i0MnRRAUt46hPMI4vE4+Hw+ec7t9/44VgWigEeby+UgFArJWjUYOqhWG6x50rpcSfR6PVUfNOgEVRlTX0HhrZBKz4MZjUYWi8VoA+lc9H/VaRZYjBKrtXR8tlwumcFgeMWRbZpA9ORQWfVm8A/FsrLaxebd5wAAAABJRU5ErkJggg=="; - -// Tool configuration -const name = "get-tiny-image"; -const config = { - title: "Get Tiny Image Tool", - description: "Returns a tiny MCP logo image.", - inputSchema: {}, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, -}; - -/** - * Registers the "get-tiny-image" tool. - * - * The registered tool returns a response containing a small image alongside some - * descriptive text. - * - * The response structure includes textual content before and after the image. - * The image is served as a PNG data type and represents the default MCP tiny image. - * - * @param server - The McpServer instance where the tool will be registered. - */ -export const registerGetTinyImageTool = (server: McpServer) => { - server.registerTool(name, config, async (args): Promise => { - return { - content: [ - { - type: "text", - text: "Here's the image you requested:", - }, - { - type: "image", - data: MCP_TINY_IMAGE, - mimeType: "image/png", - }, - { - type: "text", - text: "The image above is the MCP logo.", - }, - ], - }; - }); -}; diff --git a/src/everything/tools/gzip-file-as-resource.ts b/src/everything/tools/gzip-file-as-resource.ts deleted file mode 100644 index 3dd6fda..0000000 --- a/src/everything/tools/gzip-file-as-resource.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult, Resource } from "@modelcontextprotocol/sdk/types.js"; -import { gzipSync } from "node:zlib"; -import { - getSessionResourceURI, - registerSessionResource, -} from "../resources/session.js"; - -// Maximum input file size - 10 MB default -const GZIP_MAX_FETCH_SIZE = Number( - process.env.GZIP_MAX_FETCH_SIZE ?? String(10 * 1024 * 1024) -); - -// Maximum fetch time - 30 seconds default. -const GZIP_MAX_FETCH_TIME_MILLIS = Number( - process.env.GZIP_MAX_FETCH_TIME_MILLIS ?? String(30 * 1000) -); - -// Comma-separated list of allowed domains. Empty means all domains are allowed. -const GZIP_ALLOWED_DOMAINS = (process.env.GZIP_ALLOWED_DOMAINS ?? "") - .split(",") - .map((d) => d.trim().toLowerCase()) - .filter((d) => d.length > 0); - -// Tool input schema -const GZipFileAsResourceSchema = z.object({ - name: z.string().describe("Name of the output file").default("README.md.gz"), - data: z - .url() - .describe("URL or data URI of the file content to compress") - .default( - "https://raw.githubusercontent.com/modelcontextprotocol/servers/refs/heads/main/README.md" - ), - outputType: z - .enum(["resourceLink", "resource"]) - .default("resourceLink") - .describe( - "How the resulting gzipped file should be returned. 'resourceLink' returns a link to a resource that can be read later, 'resource' returns a full resource object." - ), -}); - -// Tool configuration -const name = "gzip-file-as-resource"; -const config = { - title: "GZip File as Resource Tool", - description: - "Compresses a single file using gzip compression. Depending upon the selected output type, returns either the compressed data as a gzipped resource or a resource link, allowing it to be downloaded in a subsequent request during the current session.", - inputSchema: GZipFileAsResourceSchema, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, -}; - -/** - * Registers the `gzip-file-as-resource` tool. - * - * The registered tool compresses input data using gzip, and makes the resulting file accessible - * as a resource for the duration of the session. - * - * The tool supports two output types: - * - "resource": Returns the resource directly, including its URI, MIME type, and base64-encoded content. - * - "resourceLink": Returns a link to access the resource later. - * - * If an unrecognized `outputType` is provided, the tool throws an error. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - * @throws {Error} Throws an error if an unknown output type is specified. - */ -export const registerGZipFileAsResourceTool = (server: McpServer) => { - server.registerTool(name, config, async (args): Promise => { - const { - name, - data: dataUri, - outputType, - } = GZipFileAsResourceSchema.parse(args); - - // Validate data uri - const url = validateDataURI(dataUri); - - // Fetch the data - const response = await fetchSafely(url, { - maxBytes: GZIP_MAX_FETCH_SIZE, - timeoutMillis: GZIP_MAX_FETCH_TIME_MILLIS, - }); - - // Compress the data using gzip - const inputBuffer = Buffer.from(response); - const compressedBuffer = gzipSync(inputBuffer); - - // Create resource - const uri = getSessionResourceURI(name); - const blob = compressedBuffer.toString("base64"); - const mimeType = "application/gzip"; - const resource = { uri, name, mimeType }; - - // Register resource, get resource link in return - const resourceLink = registerSessionResource( - server, - resource, - "blob", - blob - ); - - // Return the resource or a resource link that can be used to access this resource later - if (outputType === "resource") { - return { - content: [ - { - type: "resource", - resource: { uri, mimeType, blob }, - }, - ], - }; - } else if (outputType === "resourceLink") { - return { - content: [resourceLink], - }; - } else { - throw new Error(`Unknown outputType: ${outputType}`); - } - }); -}; - -/** - * Validates a given data URI to ensure it follows the appropriate protocols and rules. - * - * @param {string} dataUri - The data URI to validate. Must be an HTTP, HTTPS, or data protocol URL. If a domain is provided, it must match the allowed domains list if applicable. - * @return {URL} The validated and parsed URL object. - * @throws {Error} If the data URI does not use a supported protocol or does not meet allowed domains criteria. - */ -function validateDataURI(dataUri: string): URL { - // Validate Inputs - const url = new URL(dataUri); - try { - if ( - url.protocol !== "http:" && - url.protocol !== "https:" && - url.protocol !== "data:" - ) { - throw new Error( - `Unsupported URL protocol for ${dataUri}. Only http, https, and data URLs are supported.` - ); - } - if ( - GZIP_ALLOWED_DOMAINS.length > 0 && - (url.protocol === "http:" || url.protocol === "https:") - ) { - const domain = url.hostname; - const domainAllowed = GZIP_ALLOWED_DOMAINS.some((allowedDomain) => { - return domain === allowedDomain || domain.endsWith(`.${allowedDomain}`); - }); - if (!domainAllowed) { - throw new Error(`Domain ${domain} is not in the allowed domains list.`); - } - } - } catch (error) { - throw new Error( - `Error processing file ${dataUri}: ${ - error instanceof Error ? error.message : String(error) - }` - ); - } - return url; -} - -/** - * Fetches data safely from a given URL while ensuring constraints on maximum byte size and timeout duration. - * - * @param {URL} url The URL to fetch data from. - * @param {Object} options An object containing options for the fetch operation. - * @param {number} options.maxBytes The maximum allowed size (in bytes) of the response. If the response exceeds this size, the operation will be aborted. - * @param {number} options.timeoutMillis The timeout duration (in milliseconds) for the fetch operation. If the fetch takes longer, it will be aborted. - * @return {Promise} A promise that resolves with the response as an ArrayBuffer if successful. - * @throws {Error} Throws an error if the response size exceeds the defined limit, the fetch times out, or the response is otherwise invalid. - */ -async function fetchSafely( - url: URL, - { maxBytes, timeoutMillis }: { maxBytes: number; timeoutMillis: number } -): Promise { - const controller = new AbortController(); - const timeout = setTimeout( - () => - controller.abort( - `Fetching ${url} took more than ${timeoutMillis} ms and was aborted.` - ), - timeoutMillis - ); - - try { - // Fetch the data - const response = await fetch(url, { signal: controller.signal }); - if (!response.body) { - throw new Error("No response body"); - } - - // Note: we can't trust the Content-Length header: a malicious or clumsy server could return much more data than advertised. - // We check it here for early bail-out, but we still need to monitor actual bytes read below. - const contentLengthHeader = response.headers.get("content-length"); - if (contentLengthHeader != null) { - const contentLength = parseInt(contentLengthHeader, 10); - if (contentLength > maxBytes) { - throw new Error( - `Content-Length for ${url} exceeds max of ${maxBytes}: ${contentLength}` - ); - } - } - - // Read the fetched data from the response body - const reader = response.body.getReader(); - const chunks = []; - let totalSize = 0; - - // Read chunks until done - try { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - totalSize += value.length; - - if (totalSize > maxBytes) { - reader.cancel(); - throw new Error(`Response from ${url} exceeds ${maxBytes} bytes`); - } - - chunks.push(value); - } - } finally { - reader.releaseLock(); - } - - // Combine chunks into a single buffer - const buffer = new Uint8Array(totalSize); - let offset = 0; - for (const chunk of chunks) { - buffer.set(chunk, offset); - offset += chunk.length; - } - - return buffer.buffer; - } finally { - clearTimeout(timeout); - } -} diff --git a/src/everything/tools/index.ts b/src/everything/tools/index.ts deleted file mode 100644 index 0272126..0000000 --- a/src/everything/tools/index.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerGetAnnotatedMessageTool } from "./get-annotated-message.js"; -import { registerEchoTool } from "./echo.js"; -import { registerGetEnvTool } from "./get-env.js"; -import { registerGetResourceLinksTool } from "./get-resource-links.js"; -import { registerGetResourceReferenceTool } from "./get-resource-reference.js"; -import { registerGetRootsListTool } from "./get-roots-list.js"; -import { registerGetStructuredContentTool } from "./get-structured-content.js"; -import { registerGetSumTool } from "./get-sum.js"; -import { registerGetTinyImageTool } from "./get-tiny-image.js"; -import { registerGZipFileAsResourceTool } from "./gzip-file-as-resource.js"; -import { registerToggleSimulatedLoggingTool } from "./toggle-simulated-logging.js"; -import { registerToggleSubscriberUpdatesTool } from "./toggle-subscriber-updates.js"; -import { registerTriggerElicitationRequestTool } from "./trigger-elicitation-request.js"; -import { registerTriggerLongRunningOperationTool } from "./trigger-long-running-operation.js"; -import { registerTriggerSamplingRequestTool } from "./trigger-sampling-request.js"; -import { registerTriggerSamplingRequestAsyncTool } from "./trigger-sampling-request-async.js"; -import { registerTriggerElicitationRequestAsyncTool } from "./trigger-elicitation-request-async.js"; -import { registerSimulateResearchQueryTool } from "./simulate-research-query.js"; -import { registerTriggerUrlElicitationTool } from "./trigger-url-elicitation.js"; - -/** - * Register the tools with the MCP server. - * @param server - */ -export const registerTools = (server: McpServer) => { - registerEchoTool(server); - registerGetAnnotatedMessageTool(server); - registerGetEnvTool(server); - registerGetResourceLinksTool(server); - registerGetResourceReferenceTool(server); - registerGetStructuredContentTool(server); - registerGetSumTool(server); - registerGetTinyImageTool(server); - registerGZipFileAsResourceTool(server); - registerToggleSimulatedLoggingTool(server); - registerToggleSubscriberUpdatesTool(server); - registerTriggerLongRunningOperationTool(server); -}; - -/** - * Register the tools that are conditional upon client capabilities. - * These must be registered conditionally, after initialization. - */ -export const registerConditionalTools = (server: McpServer) => { - registerGetRootsListTool(server); - registerTriggerElicitationRequestTool(server); - registerTriggerUrlElicitationTool(server); - registerTriggerSamplingRequestTool(server); - // Task-based research tool (uses experimental tasks API) - registerSimulateResearchQueryTool(server); - // Bidirectional task tools - server sends requests that client executes as tasks - registerTriggerSamplingRequestAsyncTool(server); - registerTriggerElicitationRequestAsyncTool(server); -}; diff --git a/src/everything/tools/simulate-research-query.ts b/src/everything/tools/simulate-research-query.ts deleted file mode 100644 index 098df8a..0000000 --- a/src/everything/tools/simulate-research-query.ts +++ /dev/null @@ -1,345 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - CallToolResult, - GetTaskResult, - Task, - ElicitResult, - ElicitResultSchema, -} from "@modelcontextprotocol/sdk/types.js"; -import { CreateTaskResult } from "@modelcontextprotocol/sdk/experimental/tasks"; - -// Tool input schema -const SimulateResearchQuerySchema = z.object({ - topic: z.string().describe("The research topic to investigate"), - ambiguous: z - .boolean() - .default(false) - .describe( - "Simulate an ambiguous query that requires clarification (triggers input_required status)" - ), -}); - -// Research stages -const STAGES = [ - "Gathering sources", - "Analyzing content", - "Synthesizing findings", - "Generating report", -]; - -// Duration per stage in milliseconds -const STAGE_DURATION = 1000; - -// Internal state for tracking research tasks -interface ResearchState { - topic: string; - ambiguous: boolean; - currentStage: number; - clarification?: string; - completed: boolean; - result?: CallToolResult; -} - -// Map to store research state per task -const researchStates = new Map(); - -/** - * Runs the background research process. - * Updates task status as it progresses through stages. - * If clarification is needed, sends elicitation via sendRequest with relatedTask, - * which queues the request in the task message queue. The SDK delivers it through - * the tasks/result stream when the client calls tasks/result (per spec input_required flow). - * This works on all transports (STDIO, SSE, Streamable HTTP). - */ -async function runResearchProcess( - taskId: string, - args: z.infer, - taskStore: { - updateTaskStatus: ( - taskId: string, - status: Task["status"], - message?: string - ) => Promise; - storeTaskResult: ( - taskId: string, - status: "completed" | "failed", - result: CallToolResult - ) => Promise; - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - sendRequest: any -): Promise { - const state = researchStates.get(taskId); - if (!state) return; - - // Process each stage - for (let i = state.currentStage; i < STAGES.length; i++) { - state.currentStage = i; - - // Check if task was cancelled externally - if (state.completed) return; - - // Update status message for current stage - await taskStore.updateTaskStatus(taskId, "working", `${STAGES[i]}...`); - - // At synthesis stage (index 2), check if clarification is needed - if (i === 2 && state.ambiguous && !state.clarification) { - // Update status to show we're requesting input (spec SHOULD) - await taskStore.updateTaskStatus( - taskId, - "input_required", - `Found multiple interpretations for "${state.topic}". Requesting clarification...` - ); - - try { - // relatedTask queues elicitation via task message queue โ†’ delivered through tasks/result on all transports - const elicitResult: ElicitResult = await sendRequest( - { - method: "elicitation/create", - params: { - message: `The research query "${state.topic}" could have multiple interpretations. Please clarify what you're looking for:`, - requestedSchema: { - type: "object", - properties: { - interpretation: { - type: "string", - title: "Clarification", - description: - "Which interpretation of the topic do you mean?", - oneOf: getInterpretationsForTopic(state.topic), - }, - }, - required: ["interpretation"], - }, - }, - }, - ElicitResultSchema, - { relatedTask: { taskId } } - ); - - // Process elicitation response - if (elicitResult.action === "accept" && elicitResult.content) { - state.clarification = - (elicitResult.content as { interpretation?: string }) - .interpretation || "User accepted without selection"; - } else if (elicitResult.action === "decline") { - state.clarification = "User declined - using default interpretation"; - } else { - state.clarification = "User cancelled - using default interpretation"; - } - } catch (error) { - // Elicitation failed - use default interpretation and continue - console.warn( - `Elicitation failed for task ${taskId}:`, - error instanceof Error ? error.message : String(error) - ); - state.clarification = "technical (default - elicitation unavailable)"; - } - - // Resume with working status (spec SHOULD) - await taskStore.updateTaskStatus( - taskId, - "working", - `Continuing with interpretation: "${state.clarification}"...` - ); - - // Continue processing (no return - just keep going through the loop) - } - - // Simulate work for this stage - await new Promise((resolve) => setTimeout(resolve, STAGE_DURATION)); - } - - // All stages complete - generate result - state.completed = true; - const result = generateResearchReport(state); - state.result = result; - - await taskStore.storeTaskResult(taskId, "completed", result); -} - -/** - * Generates the final research report with educational content about tasks. - */ -function generateResearchReport(state: ResearchState): CallToolResult { - const topic = state.clarification - ? `${state.topic} (${state.clarification})` - : state.topic; - - const report = `# Research Report: ${topic} - -## Research Parameters -- **Topic**: ${state.topic} -${state.clarification ? `- **Clarification**: ${state.clarification}` : ""} - -## Synthesis -This research query was processed through ${STAGES.length} stages: -${STAGES.map((s, i) => `- Stage ${i + 1}: ${s} โœ“`).join("\n")} - ---- - -## About This Demo (SEP-1686: Tasks) - -This tool demonstrates MCP's task-based execution pattern for long-running operations: - -**Task Lifecycle Demonstrated:** -1. \`tools/call\` with \`task\` parameter โ†’ Server returns \`CreateTaskResult\` (not the final result) -2. Client polls \`tasks/get\` โ†’ Server returns current status and \`statusMessage\` -3. Status progressed: \`working\` โ†’ ${ - state.clarification ? `\`input_required\` โ†’ \`working\` โ†’ ` : "" - }\`completed\` -4. Client calls \`tasks/result\` โ†’ Server returns this final result - -${ - state.clarification - ? `**Elicitation Flow:** -When the query was ambiguous, the server sent an \`elicitation/create\` request -to the client. The task status changed to \`input_required\` while awaiting user input. -${ - state.clarification.includes("unavailable") - ? `**Note:** Elicitation failed and a default interpretation was used.` - : `After receiving clarification ("${state.clarification}"), the task resumed processing and completed.` -} -` - : "" -} -**Key Concepts:** -- Tasks enable "call now, fetch later" patterns -- \`statusMessage\` provides human-readable progress updates -- Tasks have TTL (time-to-live) for automatic cleanup -- \`pollInterval\` suggests how often to check status -- Elicitation requests use \`relatedTask\` to queue via tasks/result (works on all transports) - -*This is a simulated research report from the Everything MCP Server.* -`; - - return { - content: [ - { - type: "text", - text: report, - }, - ], - }; -} - -/** - * Registers the 'simulate-research-query' tool as a task-based tool. - * - * This tool demonstrates the MCP Tasks feature (SEP-1686) with a real-world scenario: - * a research tool that gathers and synthesizes information from multiple sources. - * If the query is ambiguous, it pauses to ask for clarification before completing. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerSimulateResearchQueryTool = (server: McpServer) => { - // Check if client supports elicitation (needed for input_required flow) - const clientCapabilities = server.server.getClientCapabilities() || {}; - const clientSupportsElicitation: boolean = - clientCapabilities.elicitation !== undefined; - - server.experimental.tasks.registerToolTask( - "simulate-research-query", - { - title: "Simulate Research Query", - description: - "Simulates a deep research operation that gathers, analyzes, and synthesizes information. " + - "Demonstrates MCP task-based operations with progress through multiple stages. " + - "If 'ambiguous' is true and client supports elicitation, sends an elicitation request for clarification.", - inputSchema: SimulateResearchQuerySchema, - execution: { taskSupport: "required" }, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, - }, - { - /** - * Creates a new research task and starts background processing. - */ - createTask: async (args, extra): Promise => { - const validatedArgs = SimulateResearchQuerySchema.parse(args); - - // Create the task in the store - const task = await extra.taskStore.createTask({ - ttl: 300000, // 5 minutes - pollInterval: 1000, - }); - - // Initialize research state - const state: ResearchState = { - topic: validatedArgs.topic, - ambiguous: validatedArgs.ambiguous && clientSupportsElicitation, - currentStage: 0, - completed: false, - }; - researchStates.set(task.taskId, state); - - // Start background research (don't await - runs asynchronously) - // Pass sendRequest for elicitation (queued via task message queue, works on all transports) - runResearchProcess( - task.taskId, - validatedArgs, - extra.taskStore, - extra.sendRequest - ).catch((error) => { - console.error(`Research task ${task.taskId} failed:`, error); - extra.taskStore - .updateTaskStatus(task.taskId, "failed", String(error)) - .catch(console.error); - }); - - return { task }; - }, - - /** - * Returns the current status of the research task. - */ - getTask: async (args, extra): Promise => { - return await extra.taskStore.getTask(extra.taskId); - }, - - /** - * Returns the task result. - * Elicitation is now handled directly in the background process. - */ - getTaskResult: async (args, extra): Promise => { - // Return the stored result - const result = await extra.taskStore.getTaskResult(extra.taskId); - - // Clean up state - researchStates.delete(extra.taskId); - - return result as CallToolResult; - }, - } - ); -}; - -/** - * Returns contextual interpretation options based on the topic. - */ -function getInterpretationsForTopic( - topic: string -): Array<{ const: string; title: string }> { - const lowerTopic = topic.toLowerCase(); - - // Example: contextual interpretations for "python" - if (lowerTopic.includes("python")) { - return [ - { const: "programming", title: "Python programming language" }, - { const: "snake", title: "Python snake species" }, - { const: "comedy", title: "Monty Python comedy group" }, - ]; - } - - // Default generic interpretations - return [ - { const: "technical", title: "Technical/scientific perspective" }, - { const: "historical", title: "Historical perspective" }, - { const: "current", title: "Current events/news perspective" }, - ]; -} diff --git a/src/everything/tools/toggle-simulated-logging.ts b/src/everything/tools/toggle-simulated-logging.ts deleted file mode 100644 index eb9a4b1..0000000 --- a/src/everything/tools/toggle-simulated-logging.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { - beginSimulatedLogging, - stopSimulatedLogging, -} from "../server/logging.js"; - -// Tool configuration -const name = "toggle-simulated-logging"; -const config = { - title: "Toggle Simulated Logging", - description: "Toggles simulated, random-leveled logging on or off.", - inputSchema: {}, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, -}; - -// Track enabled clients by session id -const clients: Set = new Set(); - -/** - * Registers the `toggle-simulated-logging` tool. - * - * The registered tool enables or disables the sending of periodic, random-leveled - * logging messages the connected client. - * - * When invoked, it either starts or stops simulated logging based on the session's - * current state. If logging for the specified session is active, it will be stopped; - * if it is inactive, logging will be started. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerToggleSimulatedLoggingTool = (server: McpServer) => { - server.registerTool( - name, - config, - async (_args, extra): Promise => { - const sessionId = extra?.sessionId; - - let response: string; - if (clients.has(sessionId)) { - stopSimulatedLogging(sessionId); - clients.delete(sessionId); - response = `Stopped simulated logging for session ${sessionId}`; - } else { - beginSimulatedLogging(server, sessionId); - clients.add(sessionId); - response = `Started simulated, random-leveled logging for session ${sessionId} at a 5 second pace. Client's selected logging level will be respected. If an interval elapses and the message to be sent is below the selected level, it will not be sent. Thus at higher chosen logging levels, messages should arrive further apart. `; - } - - return { - content: [{ type: "text", text: `${response}` }], - }; - } - ); -}; diff --git a/src/everything/tools/toggle-subscriber-updates.ts b/src/everything/tools/toggle-subscriber-updates.ts deleted file mode 100644 index 759ec20..0000000 --- a/src/everything/tools/toggle-subscriber-updates.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { - beginSimulatedResourceUpdates, - stopSimulatedResourceUpdates, -} from "../resources/subscriptions.js"; - -// Tool configuration -const name = "toggle-subscriber-updates"; -const config = { - title: "Toggle Subscriber Updates", - description: "Toggles simulated resource subscription updates on or off.", - inputSchema: {}, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, -}; - -// Track enabled clients by session id -const clients: Set = new Set(); - -/** - * Registers the `toggle-subscriber-updates` tool. - * - * The registered tool enables or disables the sending of periodic, simulated resource - * update messages the connected client for any subscriptions they have made. - * - * When invoked, it either starts or stops simulated resource updates based on the session's - * current state. If simulated updates for the specified session is active, it will be stopped; - * if it is inactive, simulated updates will be started. - * - * The response provides feedback indicating whether simulated updates were started or stopped, - * including the session ID. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerToggleSubscriberUpdatesTool = (server: McpServer) => { - server.registerTool( - name, - config, - async (_args, extra): Promise => { - const sessionId = extra?.sessionId; - - let response: string; - if (clients.has(sessionId)) { - stopSimulatedResourceUpdates(sessionId); - clients.delete(sessionId); - response = `Stopped simulated resource updates for session ${sessionId}`; - } else { - beginSimulatedResourceUpdates(server, sessionId); - clients.add(sessionId); - response = `Started simulated resource updated notifications for session ${sessionId} at a 5 second pace. Client will receive updates for any resources the it is subscribed to.`; - } - - return { - content: [{ type: "text", text: `${response}` }], - }; - } - ); -}; diff --git a/src/everything/tools/trigger-elicitation-request-async.ts b/src/everything/tools/trigger-elicitation-request-async.ts deleted file mode 100644 index d6e61b7..0000000 --- a/src/everything/tools/trigger-elicitation-request-async.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; - -// Tool configuration -const name = "trigger-elicitation-request-async"; -const config = { - title: "Trigger Async Elicitation Request Tool", - description: - "Trigger an async elicitation request that the CLIENT executes as a background task. " + - "Demonstrates bidirectional MCP tasks where the server sends an elicitation request and " + - "the client handles user input asynchronously, allowing the server to poll for completion.", - inputSchema: {}, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, -}; - -// Poll interval in milliseconds -const POLL_INTERVAL = 1000; - -// Maximum poll attempts before timeout (10 minutes for user input) -const MAX_POLL_ATTEMPTS = 600; - -/** - * Registers the 'trigger-elicitation-request-async' tool. - * - * This tool demonstrates bidirectional MCP tasks for elicitation: - * - Server sends elicitation request to client with task metadata - * - Client creates a task and returns CreateTaskResult - * - Client prompts user for input (task status: input_required) - * - Server polls client's tasks/get endpoint for status - * - Server fetches final result from client's tasks/result endpoint - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerTriggerElicitationRequestAsyncTool = ( - server: McpServer -) => { - // Check client capabilities - const clientCapabilities = server.server.getClientCapabilities() || {}; - - // Client must support elicitation AND tasks.requests.elicitation - const clientSupportsElicitation = - clientCapabilities.elicitation !== undefined; - const clientTasksCapability = clientCapabilities.tasks as - | { - requests?: { elicitation?: { create?: object } }; - } - | undefined; - const clientSupportsAsyncElicitation = - clientTasksCapability?.requests?.elicitation?.create !== undefined; - - if (clientSupportsElicitation && clientSupportsAsyncElicitation) { - server.registerTool( - name, - config, - async (args, extra): Promise => { - // Create the elicitation request WITH task metadata - // Using z.any() schema to avoid complex type matching with _meta - const request = { - method: "elicitation/create" as const, - params: { - task: { - ttl: 600000, // 10 minutes (user input may take a while) - }, - message: - "Please provide inputs for the following fields (async task demo):", - requestedSchema: { - type: "object" as const, - properties: { - name: { - title: "Your Name", - type: "string" as const, - description: "Your full name", - }, - favoriteColor: { - title: "Favorite Color", - type: "string" as const, - description: "What is your favorite color?", - enum: ["Red", "Blue", "Green", "Yellow", "Purple"], - }, - agreeToTerms: { - title: "Terms Agreement", - type: "boolean" as const, - description: "Do you agree to the terms and conditions?", - }, - }, - required: ["name"], - }, - }, - }; - - // Send the elicitation request - // Client may return either: - // - ElicitResult (synchronous execution) - // - CreateTaskResult (task-based execution with { task } object) - const elicitResponse = await extra.sendRequest( - request as Parameters[0], - z.union([ - // CreateTaskResult - client created a task - z.object({ - task: z.object({ - taskId: z.string(), - status: z.string(), - pollInterval: z.number().optional(), - statusMessage: z.string().optional(), - }), - }), - // ElicitResult - synchronous execution - z.object({ - action: z.string(), - content: z.any().optional(), - }), - ]) - ); - - // Check if client returned CreateTaskResult (has task object) - const isTaskResult = "task" in elicitResponse && elicitResponse.task; - if (!isTaskResult) { - // Client executed synchronously - return the direct response - return { - content: [ - { - type: "text", - text: `[SYNC] Client executed synchronously:\n${JSON.stringify( - elicitResponse, - null, - 2 - )}`, - }, - ], - }; - } - - const taskId = elicitResponse.task.taskId; - const statusMessages: string[] = []; - statusMessages.push(`Task created: ${taskId}`); - - // Poll for task completion - let attempts = 0; - let taskStatus = elicitResponse.task.status; - let taskStatusMessage: string | undefined; - - while ( - taskStatus !== "completed" && - taskStatus !== "failed" && - taskStatus !== "cancelled" && - attempts < MAX_POLL_ATTEMPTS - ) { - // Wait before polling - await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL)); - attempts++; - - // Get task status from client - const pollResult = await extra.sendRequest( - { - method: "tasks/get", - params: { taskId }, - }, - z.looseObject({ - status: z.string(), - statusMessage: z.string().optional(), - }) - ); - - taskStatus = pollResult.status; - taskStatusMessage = pollResult.statusMessage; - - // Only log status changes or every 10 polls to avoid spam - if ( - attempts === 1 || - attempts % 10 === 0 || - taskStatus !== "input_required" - ) { - statusMessages.push( - `Poll ${attempts}: ${taskStatus}${ - taskStatusMessage ? ` - ${taskStatusMessage}` : "" - }` - ); - } - } - - // Check for timeout - if (attempts >= MAX_POLL_ATTEMPTS) { - return { - content: [ - { - type: "text", - text: `[TIMEOUT] Task timed out after ${MAX_POLL_ATTEMPTS} poll attempts\n\nProgress:\n${statusMessages.join( - "\n" - )}`, - }, - ], - }; - } - - // Check for failure/cancellation - if (taskStatus === "failed" || taskStatus === "cancelled") { - return { - content: [ - { - type: "text", - text: `[${taskStatus.toUpperCase()}] ${ - taskStatusMessage || "No message" - }\n\nProgress:\n${statusMessages.join("\n")}`, - }, - ], - }; - } - - // Fetch the final result - const result = await extra.sendRequest( - { - method: "tasks/result", - params: { taskId }, - }, - z.any() - ); - - // Format the elicitation result - const content: CallToolResult["content"] = []; - - if (result.action === "accept" && result.content) { - content.push({ - type: "text", - text: `[COMPLETED] User provided the requested information!`, - }); - - const userData = result.content as Record; - const lines = []; - if (userData.name) lines.push(`- Name: ${userData.name}`); - if (userData.favoriteColor) - lines.push(`- Favorite Color: ${userData.favoriteColor}`); - if (userData.agreeToTerms !== undefined) - lines.push(`- Agreed to terms: ${userData.agreeToTerms}`); - - content.push({ - type: "text", - text: `User inputs:\n${lines.join("\n")}`, - }); - } else if (result.action === "decline") { - content.push({ - type: "text", - text: `[DECLINED] User declined to provide the requested information.`, - }); - } else if (result.action === "cancel") { - content.push({ - type: "text", - text: `[CANCELLED] User cancelled the elicitation dialog.`, - }); - } - - // Include progress and raw result for debugging - content.push({ - type: "text", - text: `\nProgress:\n${statusMessages.join( - "\n" - )}\n\nRaw result: ${JSON.stringify(result, null, 2)}`, - }); - - return { content }; - } - ); - } -}; diff --git a/src/everything/tools/trigger-elicitation-request.ts b/src/everything/tools/trigger-elicitation-request.ts deleted file mode 100644 index ca47421..0000000 --- a/src/everything/tools/trigger-elicitation-request.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - ElicitResultSchema, - CallToolResult, -} from "@modelcontextprotocol/sdk/types.js"; - -// Tool configuration -const name = "trigger-elicitation-request"; -const config = { - title: "Trigger Elicitation Request Tool", - description: "Trigger a Request from the Server for User Elicitation", - inputSchema: {}, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, -}; - -/** - * Registers the 'trigger-elicitation-request' tool. - * - * If the client does not support the elicitation capability, the tool is not registered. - * - * The registered tool sends an elicitation request for the user to provide information - * based on a pre-defined schema of fields including text inputs, booleans, numbers, - * email, dates, enums of various types, etc. It uses validation and handles multiple - * possible outcomes from the user's response, such as acceptance with content, decline, - * or cancellation of the dialog. The process also ensures parsing and validating - * the elicitation input arguments at runtime. - * - * The elicitation dialog response is returned, formatted into a structured result, - * which contains both user-submitted input data (if provided) and debugging information, - * including raw results. - * - * @param {McpServer} server - TThe McpServer instance where the tool will be registered. - */ -export const registerTriggerElicitationRequestTool = (server: McpServer) => { - // Does the client support elicitation? - const clientCapabilities = server.server.getClientCapabilities() || {}; - const clientSupportsElicitation: boolean = - clientCapabilities.elicitation !== undefined; - - // If so, register tool - if (clientSupportsElicitation) { - server.registerTool( - name, - config, - async (args, extra): Promise => { - const elicitationResult = await extra.sendRequest( - { - method: "elicitation/create", - params: { - message: "Please provide inputs for the following fields:", - requestedSchema: { - type: "object", - properties: { - name: { - title: "String", - type: "string", - description: "Your full, legal name", - }, - check: { - title: "Boolean", - type: "boolean", - description: "Agree to the terms and conditions", - }, - firstLine: { - title: "String with default", - type: "string", - description: "Favorite first line of a story", - default: "It was a dark and stormy night.", - }, - email: { - title: "String with email format", - type: "string", - format: "email", - description: - "Your email address (will be verified, and never shared with anyone else)", - }, - homepage: { - type: "string", - format: "uri", - title: "String with uri format", - description: "Portfolio / personal website", - }, - birthdate: { - title: "String with date format", - type: "string", - format: "date", - description: "Your date of birth", - }, - integer: { - title: "Integer", - type: "integer", - description: - "Your favorite integer (do not give us your phone number, pin, or other sensitive info)", - minimum: 1, - maximum: 100, - default: 42, - }, - number: { - title: "Number in range 1-1000", - type: "number", - description: "Favorite number (there are no wrong answers)", - minimum: 0, - maximum: 1000, - default: 3.14, - }, - untitledSingleSelectEnum: { - type: "string", - title: "Untitled Single Select Enum", - description: "Choose your favorite friend", - enum: [ - "Monica", - "Rachel", - "Joey", - "Chandler", - "Ross", - "Phoebe", - ], - default: "Monica", - }, - untitledMultipleSelectEnum: { - type: "array", - title: "Untitled Multiple Select Enum", - description: "Choose your favorite instruments", - minItems: 1, - maxItems: 3, - items: { - type: "string", - enum: ["Guitar", "Piano", "Violin", "Drums", "Bass"], - }, - default: ["Guitar"], - }, - titledSingleSelectEnum: { - type: "string", - title: "Titled Single Select Enum", - description: "Choose your favorite hero", - oneOf: [ - { const: "hero-1", title: "Superman" }, - { const: "hero-2", title: "Green Lantern" }, - { const: "hero-3", title: "Wonder Woman" }, - ], - default: "hero-1", - }, - titledMultipleSelectEnum: { - type: "array", - title: "Titled Multiple Select Enum", - description: "Choose your favorite types of fish", - minItems: 1, - maxItems: 3, - items: { - anyOf: [ - { const: "fish-1", title: "Tuna" }, - { const: "fish-2", title: "Salmon" }, - { const: "fish-3", title: "Trout" }, - ], - }, - default: ["fish-1"], - }, - legacyTitledEnum: { - type: "string", - title: "Legacy Titled Single Select Enum", - description: "Choose your favorite type of pet", - enum: ["pet-1", "pet-2", "pet-3", "pet-4", "pet-5"], - enumNames: ["Cats", "Dogs", "Birds", "Fish", "Reptiles"], - default: "pet-1", - }, - }, - required: ["name"], - }, - }, - }, - ElicitResultSchema, - { timeout: 10 * 60 * 1000 /* 10 minutes */ } - ); - - // Handle different response actions - const content: CallToolResult["content"] = []; - - if ( - elicitationResult.action === "accept" && - elicitationResult.content - ) { - content.push({ - type: "text", - text: `โœ… User provided the requested information!`, - }); - - // Only access elicitationResult.content when action is accept - const userData = elicitationResult.content; - const lines = []; - if (userData.name) lines.push(`- Name: ${userData.name}`); - if (userData.check !== undefined) - lines.push(`- Agreed to terms: ${userData.check}`); - if (userData.color) lines.push(`- Favorite Color: ${userData.color}`); - if (userData.email) lines.push(`- Email: ${userData.email}`); - if (userData.homepage) lines.push(`- Homepage: ${userData.homepage}`); - if (userData.birthdate) - lines.push(`- Birthdate: ${userData.birthdate}`); - if (userData.integer !== undefined) - lines.push(`- Favorite Integer: ${userData.integer}`); - if (userData.number !== undefined) - lines.push(`- Favorite Number: ${userData.number}`); - if (userData.petType) lines.push(`- Pet Type: ${userData.petType}`); - - content.push({ - type: "text", - text: `User inputs:\n${lines.join("\n")}`, - }); - } else if (elicitationResult.action === "decline") { - content.push({ - type: "text", - text: `โŒ User declined to provide the requested information.`, - }); - } else if (elicitationResult.action === "cancel") { - content.push({ - type: "text", - text: `โš ๏ธ User cancelled the elicitation dialog.`, - }); - } - - // Include raw result for debugging - content.push({ - type: "text", - text: `\nRaw result: ${JSON.stringify(elicitationResult, null, 2)}`, - }); - - return { content }; - } - ); - } -}; diff --git a/src/everything/tools/trigger-long-running-operation.ts b/src/everything/tools/trigger-long-running-operation.ts deleted file mode 100644 index 95415e8..0000000 --- a/src/everything/tools/trigger-long-running-operation.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; - -// Tool input schema -const TriggerLongRunningOperationSchema = z.object({ - duration: z - .number() - .default(10) - .describe("Duration of the operation in seconds"), - steps: z.number().default(5).describe("Number of steps in the operation"), -}); - -// Tool configuration -const name = "trigger-long-running-operation"; -const config = { - title: "Trigger Long Running Operation Tool", - description: "Demonstrates a long running operation with progress updates.", - inputSchema: TriggerLongRunningOperationSchema, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, -}; - -/** - * Registers the 'trigger-tong-running-operation' tool. - * - * The registered tool starts a long-running operation defined by a specific duration and - * number of steps. - * - * Progress notifications are sent back to the client at each step if a `progressToken` - * is provided in the metadata. - * - * At the end of the operation, the tool returns a message indicating the completion of the - * operation, including the total duration and steps. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerTriggerLongRunningOperationTool = (server: McpServer) => { - server.registerTool( - name, - config, - async (args, extra): Promise => { - const validatedArgs = TriggerLongRunningOperationSchema.parse(args); - const { duration, steps } = validatedArgs; - const stepDuration = duration / steps; - const progressToken = extra._meta?.progressToken; - - for (let i = 1; i < steps + 1; i++) { - await new Promise((resolve) => - setTimeout(resolve, stepDuration * 1000) - ); - - if (progressToken !== undefined) { - await server.server.notification( - { - method: "notifications/progress", - params: { - progress: i, - total: steps, - progressToken, - }, - }, - { relatedRequestId: extra.requestId } - ); - } - } - - return { - content: [ - { - type: "text", - text: `Long running operation completed. Duration: ${duration} seconds, Steps: ${steps}.`, - }, - ], - }; - } - ); -}; diff --git a/src/everything/tools/trigger-sampling-request-async.ts b/src/everything/tools/trigger-sampling-request-async.ts deleted file mode 100644 index d61dadd..0000000 --- a/src/everything/tools/trigger-sampling-request-async.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - CallToolResult, - CreateMessageRequest, -} from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; - -// Tool input schema -const TriggerSamplingRequestAsyncSchema = z.object({ - prompt: z.string().describe("The prompt to send to the LLM"), - maxTokens: z - .number() - .default(100) - .describe("Maximum number of tokens to generate"), -}); - -// Tool configuration -const name = "trigger-sampling-request-async"; -const config = { - title: "Trigger Async Sampling Request Tool", - description: - "Trigger an async sampling request that the CLIENT executes as a background task. " + - "Demonstrates bidirectional MCP tasks where the server sends a request and the client " + - "executes it asynchronously, allowing the server to poll for progress and results.", - inputSchema: TriggerSamplingRequestAsyncSchema, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, -}; - -// Poll interval in milliseconds -const POLL_INTERVAL = 1000; - -// Maximum poll attempts before timeout -const MAX_POLL_ATTEMPTS = 60; - -/** - * Registers the 'trigger-sampling-request-async' tool. - * - * This tool demonstrates bidirectional MCP tasks: - * - Server sends sampling request to client with task metadata - * - Client creates a task and returns CreateTaskResult - * - Server polls client's tasks/get endpoint for status - * - Server fetches final result from client's tasks/result endpoint - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerTriggerSamplingRequestAsyncTool = (server: McpServer) => { - // Check client capabilities - const clientCapabilities = server.server.getClientCapabilities() || {}; - - // Client must support sampling AND tasks.requests.sampling - const clientSupportsSampling = clientCapabilities.sampling !== undefined; - const clientTasksCapability = clientCapabilities.tasks as - | { - requests?: { sampling?: { createMessage?: object } }; - } - | undefined; - const clientSupportsAsyncSampling = - clientTasksCapability?.requests?.sampling?.createMessage !== undefined; - - if (clientSupportsSampling && clientSupportsAsyncSampling) { - server.registerTool( - name, - config, - async (args, extra): Promise => { - const validatedArgs = TriggerSamplingRequestAsyncSchema.parse(args); - const { prompt, maxTokens } = validatedArgs; - - // Create the sampling request WITH task metadata - // The params.task field signals to the client that this should be executed as a task - const request: CreateMessageRequest & { - params: { task?: { ttl: number } }; - } = { - method: "sampling/createMessage", - params: { - task: { - ttl: 300000, // 5 minutes - }, - messages: [ - { - role: "user", - content: { - type: "text", - text: `Resource ${name} context: ${prompt}`, - }, - }, - ], - systemPrompt: "You are a helpful test server.", - maxTokens, - temperature: 0.7, - }, - }; - - // Send the sampling request - // Client may return either: - // - CreateMessageResult (synchronous execution) - // - CreateTaskResult (task-based execution with { task } object) - const samplingResponse = await extra.sendRequest( - request, - z.union([ - // CreateTaskResult - client created a task - z.object({ - task: z.object({ - taskId: z.string(), - status: z.string(), - pollInterval: z.number().optional(), - statusMessage: z.string().optional(), - }), - }), - // CreateMessageResult - synchronous execution - z.object({ - role: z.string(), - content: z.any(), - model: z.string(), - stopReason: z.string().optional(), - }), - ]) - ); - - // Check if client returned CreateTaskResult (has task object) - const isTaskResult = - "task" in samplingResponse && samplingResponse.task; - if (!isTaskResult) { - // Client executed synchronously - return the direct response - return { - content: [ - { - type: "text", - text: `[SYNC] Client executed synchronously:\n${JSON.stringify( - samplingResponse, - null, - 2 - )}`, - }, - ], - }; - } - - const taskId = samplingResponse.task.taskId; - const statusMessages: string[] = []; - statusMessages.push(`Task created: ${taskId}`); - - // Poll for task completion - let attempts = 0; - let taskStatus = samplingResponse.task.status; - let taskStatusMessage: string | undefined; - - while ( - taskStatus !== "completed" && - taskStatus !== "failed" && - taskStatus !== "cancelled" && - attempts < MAX_POLL_ATTEMPTS - ) { - // Wait before polling - await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL)); - attempts++; - - // Get task status from client - const pollResult = await extra.sendRequest( - { - method: "tasks/get", - params: { taskId }, - }, - z.looseObject({ - status: z.string(), - statusMessage: z.string().optional(), - }) - ); - - taskStatus = pollResult.status; - taskStatusMessage = pollResult.statusMessage; - statusMessages.push( - `Poll ${attempts}: ${taskStatus}${ - taskStatusMessage ? ` - ${taskStatusMessage}` : "" - }` - ); - } - - // Check for timeout - if (attempts >= MAX_POLL_ATTEMPTS) { - return { - content: [ - { - type: "text", - text: `[TIMEOUT] Task timed out after ${MAX_POLL_ATTEMPTS} poll attempts\n\nProgress:\n${statusMessages.join( - "\n" - )}`, - }, - ], - }; - } - - // Check for failure/cancellation - if (taskStatus === "failed" || taskStatus === "cancelled") { - return { - content: [ - { - type: "text", - text: `[${taskStatus.toUpperCase()}] ${ - taskStatusMessage || "No message" - }\n\nProgress:\n${statusMessages.join("\n")}`, - }, - ], - }; - } - - // Fetch the final result - const result = await extra.sendRequest( - { - method: "tasks/result", - params: { taskId }, - }, - z.any() - ); - - // Return the result with status history - return { - content: [ - { - type: "text", - text: `[COMPLETED] Async sampling completed!\n\n**Progress:**\n${statusMessages.join( - "\n" - )}\n\n**Result:**\n${JSON.stringify(result, null, 2)}`, - }, - ], - }; - } - ); - } -}; diff --git a/src/everything/tools/trigger-sampling-request.ts b/src/everything/tools/trigger-sampling-request.ts deleted file mode 100644 index fd9b537..0000000 --- a/src/everything/tools/trigger-sampling-request.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - CallToolResult, - CreateMessageRequest, - CreateMessageResultSchema, -} from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; - -// Tool input schema -const TriggerSamplingRequestSchema = z.object({ - prompt: z.string().describe("The prompt to send to the LLM"), - maxTokens: z - .number() - .default(100) - .describe("Maximum number of tokens to generate"), -}); - -// Tool configuration -const name = "trigger-sampling-request"; -const config = { - title: "Trigger Sampling Request Tool", - description: "Trigger a Request from the Server for LLM Sampling", - inputSchema: TriggerSamplingRequestSchema, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, -}; - -/** - * Registers the 'trigger-sampling-request' tool. - * - * If the client does not support the sampling capability, the tool is not registered. - * - * The registered tool performs the following operations: - * - Validates incoming arguments using `TriggerSamplingRequestSchema`. - * - Constructs a `sampling/createMessage` request object using provided prompt and maximum tokens. - * - Sends the request to the server for sampling. - * - Formats and returns the sampling result content to the client. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerTriggerSamplingRequestTool = (server: McpServer) => { - // Does the client support sampling? - const clientCapabilities = server.server.getClientCapabilities() || {}; - const clientSupportsSampling: boolean = - clientCapabilities.sampling !== undefined; - - // If so, register tool - if (clientSupportsSampling) { - server.registerTool( - name, - config, - async (args, extra): Promise => { - const validatedArgs = TriggerSamplingRequestSchema.parse(args); - const { prompt, maxTokens } = validatedArgs; - - // Create the sampling request - const request: CreateMessageRequest = { - method: "sampling/createMessage", - params: { - messages: [ - { - role: "user", - content: { - type: "text", - text: `Resource ${name} context: ${prompt}`, - }, - }, - ], - systemPrompt: "You are a helpful test server.", - maxTokens, - temperature: 0.7, - }, - }; - - // Send the sampling request to the client - const result = await extra.sendRequest( - request, - CreateMessageResultSchema - ); - - // Return the result to the client - return { - content: [ - { - type: "text", - text: `LLM sampling result: \n${JSON.stringify(result, null, 2)}`, - }, - ], - }; - } - ); - } -}; diff --git a/src/everything/tools/trigger-url-elicitation.ts b/src/everything/tools/trigger-url-elicitation.ts deleted file mode 100644 index a7de878..0000000 --- a/src/everything/tools/trigger-url-elicitation.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { - CallToolResult, - ElicitRequestURLParams, - ElicitResultSchema, - UrlElicitationRequiredError, -} from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; - -// Tool input schema -const TriggerUrlElicitationSchema = z.object({ - url: z.string().url().describe("The URL the user should open"), - message: z - .string() - .default("Please open the link to complete this action.") - .describe("Message shown to the user before opening the URL"), - elicitationId: z - .string() - .optional() - .describe("Optional explicit elicitation ID. Defaults to a random UUID."), - errorPath: z - .boolean() - .default(false) - .describe( - "Controls which elicitation mechanism is used. " + - "When false (default), sends an elicitation/create request (request path). " + - "When true, throws a UrlElicitationRequiredError (MCP error code -32042) so the client handles " + - "the URL elicitation via the error path rather than waiting for a response. " + - "To clear the error, satisfy the prerequisite and retry this call with the same arguments; the " + - "retry ignores errorPath and proceeds, so the client does not loop on the same error." - ), -}); - -// Tool configuration -const name = "trigger-url-elicitation"; -const config = { - title: "Trigger URL Elicitation Tool", - description: - "Trigger a URL elicitation so the client can direct the user to a browser flow. " + - "Supports two mechanisms: the request path (elicitation/create, default) which awaits the user's " + - "response, and the error path (UrlElicitationRequiredError, -32042) which signals the client " + - "to handle URL elicitation via the error response. Set errorPath=true to use the error path.", - inputSchema: TriggerUrlElicitationSchema, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, -}; - -/** - * Tracks requests for which an error-path prerequisite has already been issued, - * keyed by the stable inputs a client resends when it retries the original tool - * call (session + URL + caller-supplied elicitationId). - * - * When the client satisfies the prerequisite and retries the same call, the - * matching entry lets us recognize the retry, ignore `errorPath`, and proceed - * via the request path instead of re-throwing `UrlElicitationRequiredError` โ€” - * which would otherwise loop forever (throw -> client satisfies prerequisite -> - * retry -> throw -> ...). - * - * Demo simplification: entries are only removed on a recognized retry, so a - * client that triggers the error path and never retries leaves its key behind. - * That is acceptable for this reference server; a production implementation - * serving many long-lived sessions should evict entries (e.g. a - * `Map` with TTL-based cleanup). - */ -const issuedErrorPathElicitations = new Set(); - -/** - * Test-only helper to reset the module-level error-path state between cases. - * Not part of the tool's public behavior. - */ -export const __resetIssuedErrorPathElicitations = () => - issuedErrorPathElicitations.clear(); - -/** - * Registers the 'trigger-url-elicitation' tool. - * - * This tool only registers when the client advertises URL-mode elicitation - * capability (clientCapabilities.elicitation.url). - * - * Depending on the `errorPath` argument it either: - * - Sends an `elicitation/create` request and awaits the result (request path), or - * - Throws a `UrlElicitationRequiredError` (MCP error -32042) carrying a - * prerequisite elicitation for the client to handle (error path). When the - * client satisfies the prerequisite and retries the same call, the retry - * ignores `errorPath` and proceeds via the request path, so the client does - * not loop on the same error. - * - * @param {McpServer} server - The McpServer instance where the tool will be registered. - */ -export const registerTriggerUrlElicitationTool = (server: McpServer) => { - const clientCapabilities = server.server.getClientCapabilities() || {}; - const clientElicitationCapabilities = clientCapabilities.elicitation as - | { - url?: object; - } - | undefined; - - const clientSupportsUrlElicitation = - clientElicitationCapabilities?.url !== undefined; - - if (clientSupportsUrlElicitation) { - server.registerTool( - name, - config, - async (args, extra): Promise => { - const { - url, - message, - elicitationId: requestedElicitationId, - errorPath, - } = args; - - const elicitationId = requestedElicitationId ?? randomUUID(); - const sessionId = extra.sessionId ?? "default"; - - // Key the one-shot error-path marker on inputs the client resends - // verbatim when it retries the original tool call. A real client retries - // with the *same* arguments and does NOT echo the prerequisite's - // (server-generated) elicitationId, so we must key on stable inputs: - // the session, the requested URL, and the caller-supplied elicitationId - // (if any). Keying on the resolved/random elicitationId would change on - // every call and never match, re-throwing the prerequisite forever. - const errorPathKey = `${sessionId}\u0000${url}\u0000${requestedElicitationId ?? ""}`; - - const elicitationParams: ElicitRequestURLParams = { - mode: "url", - url, - message, - elicitationId, - }; - - // Error path: signal the client via UrlElicitationRequiredError (-32042) - // so it handles a prerequisite URL elicitation before this request can - // proceed. Two things keep the client from looping forever: - // - // 1. The prerequisite points at a *different* URL than the one that - // failed. Reusing the original `url` would make the client complete - // the prerequisite, retry, and hit the same -32042 error endlessly. - // 2. We remember that we issued a prerequisite for this request. When - // the client satisfies it and retries the same call, we recognize - // the retry, *ignore* errorPath, and fall through to the request - // path. Without this, the retry would re-enter the error path and - // re-request the prerequisite URL โ€” another loop. - if (errorPath) { - if (issuedErrorPathElicitations.has(errorPathKey)) { - // Retry of a satisfied prerequisite: clear the one-shot marker and - // ignore errorPath, falling through to the request path below. - issuedErrorPathElicitations.delete(errorPathKey); - } else { - // Originating call: record that we issued a prerequisite for this - // request, then signal the client via -32042. - issuedErrorPathElicitations.add(errorPathKey); - const prerequisiteElicitation: ElicitRequestURLParams = { - mode: "url", - url: "https://modelcontextprotocol.io", - message: - "Open this link to satisfy the prerequisite, then retry the request.", - elicitationId: randomUUID(), - }; - throw new UrlElicitationRequiredError( - [prerequisiteElicitation], - "This request requires browser-based authorization." - ); - } - } - - // Request path: send elicitation/create and await the user's response - const elicitationResult = await extra.sendRequest( - { - method: "elicitation/create", - params: elicitationParams, - }, - ElicitResultSchema, - { timeout: 10 * 60 * 1000 /* 10 minutes */ } - ); - - // Handle different response actions - const content: CallToolResult["content"] = []; - - if (elicitationResult.action === "accept") { - content.push({ - type: "text", - text: - `โœ… User completed the URL elicitation flow.\n` + - `Elicitation ID: ${elicitationId}\n` + - `URL: ${url}`, - }); - } else if (elicitationResult.action === "decline") { - content.push({ - type: "text", - text: `โŒ User declined to open the URL (Elicitation ID: ${elicitationId}).`, - }); - } else if (elicitationResult.action === "cancel") { - content.push({ - type: "text", - text: `โš ๏ธ User cancelled the URL elicitation (Elicitation ID: ${elicitationId}).`, - }); - } - - // Include raw result for debugging - content.push({ - type: "text", - text: `\nRaw result: ${JSON.stringify(elicitationResult, null, 2)}`, - }); - - return { content }; - } - ); - } -}; diff --git a/src/everything/transports/sse.ts b/src/everything/transports/sse.ts deleted file mode 100644 index 2406db7..0000000 --- a/src/everything/transports/sse.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; -import express from "express"; -import { createServer } from "../server/index.js"; -import cors from "cors"; - -console.error("Starting SSE server..."); - -// Express app with permissive CORS for testing with Inspector direct connect mode -const app = express(); -app.use( - cors({ - origin: "*", // use "*" with caution in production - methods: "GET,POST", - preflightContinue: false, - optionsSuccessStatus: 204, - }) -); - -// Map sessionId to transport for each client -const transports: Map = new Map< - string, - SSEServerTransport ->(); - -// Handle GET requests for new SSE streams -app.get("/sse", async (req, res) => { - let transport: SSEServerTransport; - const { server, cleanup } = createServer(); - - // Session Id should not exist for GET /sse requests - if (req?.query?.sessionId) { - const sessionId = req?.query?.sessionId as string; - transport = transports.get(sessionId) as SSEServerTransport; - console.error( - "Client Reconnecting? This shouldn't happen; when client has a sessionId, GET /sse should not be called again.", - transport.sessionId - ); - } else { - // Create and store transport for the new session - transport = new SSEServerTransport("/message", res); - transports.set(transport.sessionId, transport); - - // Connect server to transport - await server.connect(transport); - const sessionId = transport.sessionId; - console.error("Client Connected: ", sessionId); - - // Handle close of connection - server.server.onclose = async () => { - const sessionId = transport.sessionId; - console.error("Client Disconnected: ", sessionId); - transports.delete(sessionId); - cleanup(sessionId); - }; - } -}); - -// Handle POST requests for client messages -app.post("/message", async (req, res) => { - // Session Id should exist for POST /message requests - const sessionId = req?.query?.sessionId as string; - - // Get the transport for this session and use it to handle the request - const transport = transports.get(sessionId); - if (transport) { - console.error("Client Message from", sessionId); - await transport.handlePostMessage(req, res); - } else { - console.error(`No transport found for sessionId ${sessionId}`); - } -}); - -// Start the express server -const PORT = process.env.PORT || 3001; -app.listen(PORT, () => { - console.error(`Server is running on port ${PORT}`); -}); diff --git a/src/everything/transports/stdio.ts b/src/everything/transports/stdio.ts deleted file mode 100644 index 3e653bc..0000000 --- a/src/everything/transports/stdio.ts +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node - -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { createServer } from "../server/index.js"; - -console.error("Starting default (STDIO) server..."); - -/** - * The main method - * - Initializes the StdioServerTransport, sets up the server, - * - Handles cleanup on process exit. - * - * @return {Promise} A promise that resolves when the main function has executed and the process exits. - */ -async function main(): Promise { - const transport = new StdioServerTransport(); - const { server, cleanup } = createServer(); - - // Connect transport to server - await server.connect(transport); - - // Cleanup on exit - process.on("SIGINT", async () => { - await server.close(); - cleanup(); - process.exit(0); - }); -} - -main().catch((error) => { - console.error("Server error:", error); - process.exit(1); -}); diff --git a/src/everything/transports/streamableHttp.ts b/src/everything/transports/streamableHttp.ts deleted file mode 100644 index 2e79abc..0000000 --- a/src/everything/transports/streamableHttp.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { - StreamableHTTPServerTransport, - EventStore, -} from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import express, { Request, Response } from "express"; -import { createServer } from "../server/index.js"; -import { randomUUID } from "node:crypto"; -import cors from "cors"; - -// Simple in-memory event store for SSE resumability -class InMemoryEventStore implements EventStore { - private events: Map = - new Map(); - - async storeEvent(streamId: string, message: unknown): Promise { - const eventId = randomUUID(); - this.events.set(eventId, { streamId, message }); - return eventId; - } - - async replayEventsAfter( - lastEventId: string, - { send }: { send: (eventId: string, message: unknown) => Promise } - ): Promise { - const entries = Array.from(this.events.entries()); - const startIndex = entries.findIndex(([id]) => id === lastEventId); - if (startIndex === -1) return lastEventId; - - let lastId: string = lastEventId; - for (let i = startIndex + 1; i < entries.length; i++) { - const [eventId, { message }] = entries[i]; - await send(eventId, message); - lastId = eventId; - } - return lastId; - } -} - -console.log("Starting Streamable HTTP server..."); - -// Express app with permissive CORS for testing with Inspector direct connect mode -const app = express(); -app.use( - cors({ - origin: "*", // use "*" with caution in production - methods: "GET,POST,DELETE", - preflightContinue: false, - optionsSuccessStatus: 204, - exposedHeaders: ["mcp-session-id", "last-event-id", "mcp-protocol-version"], - }) -); - -// Map sessionId to server transport for each client -const transports: Map = new Map< - string, - StreamableHTTPServerTransport ->(); - -// Handle POST requests for client messages -app.post("/mcp", async (req: Request, res: Response) => { - console.log("Received MCP POST request"); - try { - // Check for existing session ID - const sessionId = req.headers["mcp-session-id"] as string | undefined; - - let transport: StreamableHTTPServerTransport; - - if (sessionId && transports.has(sessionId)) { - // Reuse existing transport - transport = transports.get(sessionId)!; - } else if (!sessionId) { - const { server, cleanup } = createServer(); - - // New initialization request - const eventStore = new InMemoryEventStore(); - transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - eventStore, // Enable resumability - onsessioninitialized: (sessionId: string) => { - // Store the transport by session ID when a session is initialized - // This avoids race conditions where requests might come in before the session is stored - console.log(`Session initialized with ID: ${sessionId}`); - transports.set(sessionId, transport); - }, - }); - - // Set up onclose handler to clean up transport when closed - server.server.onclose = async () => { - const sid = transport.sessionId; - if (sid && transports.has(sid)) { - console.log( - `Transport closed for session ${sid}, removing from transports map` - ); - transports.delete(sid); - cleanup(sid); - } - }; - - // Connect the transport to the MCP server BEFORE handling the request - // so responses can flow back through the same transport - await server.connect(transport); - await transport.handleRequest(req, res); - return; - } else { - // Invalid request - no session ID or not initialization request - res.status(400).json({ - jsonrpc: "2.0", - error: { - code: -32000, - message: "Bad Request: No valid session ID provided", - }, - id: req?.body?.id, - }); - return; - } - - // Handle the request with existing transport - no need to reconnect - // The existing transport is already connected to the server - await transport.handleRequest(req, res); - } catch (error) { - console.log("Error handling MCP request:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { - code: -32603, - message: "Internal server error", - }, - id: req?.body?.id, - }); - return; - } - } -}); - -// Handle GET requests for SSE streams -app.get("/mcp", async (req: Request, res: Response) => { - console.log("Received MCP GET request"); - const sessionId = req.headers["mcp-session-id"] as string | undefined; - if (!sessionId || !transports.has(sessionId)) { - res.status(400).json({ - jsonrpc: "2.0", - error: { - code: -32000, - message: "Bad Request: No valid session ID provided", - }, - id: req?.body?.id, - }); - return; - } - - // Check for Last-Event-ID header for resumability - const lastEventId = req.headers["last-event-id"] as string | undefined; - if (lastEventId) { - console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`); - } else { - console.log(`Establishing new SSE stream for session ${sessionId}`); - } - - const transport = transports.get(sessionId); - await transport!.handleRequest(req, res); -}); - -// Handle DELETE requests for session termination -app.delete("/mcp", async (req: Request, res: Response) => { - const sessionId = req.headers["mcp-session-id"] as string | undefined; - if (!sessionId || !transports.has(sessionId)) { - res.status(400).json({ - jsonrpc: "2.0", - error: { - code: -32000, - message: "Bad Request: No valid session ID provided", - }, - id: req?.body?.id, - }); - return; - } - - console.log(`Received session termination request for session ${sessionId}`); - - try { - const transport = transports.get(sessionId); - await transport!.handleRequest(req, res); - } catch (error) { - console.log("Error handling session termination:", error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: "2.0", - error: { - code: -32603, - message: "Error handling session termination", - }, - id: req?.body?.id, - }); - return; - } - } -}); - -// Start the server -const PORT = process.env.PORT || 3001; -const server = app.listen(PORT, () => { - console.error(`MCP Streamable HTTP Server listening on port ${PORT}`); -}); - -// Handle server errors -server.on("error", (err: unknown) => { - const code = - typeof err === "object" && err !== null && "code" in err - ? (err as { code?: unknown }).code - : undefined; - if (code === "EADDRINUSE") { - console.error( - `Failed to start: Port ${PORT} is already in use. Set PORT to a free port or stop the conflicting process.` - ); - } else { - console.error("HTTP server encountered an error while starting:", err); - } - // Ensure a non-zero exit so npm reports the failure instead of silently exiting - process.exit(1); -}); - -// Handle server shutdown -process.on("SIGINT", async () => { - console.log("Shutting down server..."); - - // Close all active transports to properly clean up resources - for (const sessionId in transports) { - try { - console.log(`Closing transport for session ${sessionId}`); - await transports.get(sessionId)!.close(); - transports.delete(sessionId); - } catch (error) { - console.log(`Error closing transport for session ${sessionId}:`, error); - } - } - - console.log("Server shutdown complete"); - process.exit(0); -}); diff --git a/src/everything/tsconfig.json b/src/everything/tsconfig.json index 829d52d..ec5da15 100644 --- a/src/everything/tsconfig.json +++ b/src/everything/tsconfig.json @@ -4,5 +4,7 @@ "outDir": "./dist", "rootDir": "." }, - "include": ["./**/*.ts"] + "include": [ + "./**/*.ts" + ] } diff --git a/src/everything/vitest.config.ts b/src/everything/vitest.config.ts deleted file mode 100644 index d414ec8..0000000 --- a/src/everything/vitest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - include: ['**/__tests__/**/*.test.ts'], - coverage: { - provider: 'v8', - include: ['**/*.ts'], - exclude: ['**/__tests__/**', '**/dist/**'], - }, - }, -}); diff --git a/src/fetch/Dockerfile b/src/fetch/Dockerfile deleted file mode 100644 index d2a4504..0000000 --- a/src/fetch/Dockerfile +++ /dev/null @@ -1,39 +0,0 @@ -# Use a Python image with uv pre-installed -FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv - -# Install the project into `/app` -WORKDIR /app - -# Enable bytecode compilation -ENV UV_COMPILE_BYTECODE=1 - -# Copy from the cache instead of linking since it's a mounted volume -ENV UV_LINK_MODE=copy - -# Install the project's dependencies using the lockfile and settings -COPY uv.lock pyproject.toml ./ -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev --no-editable - -# Then, add the rest of the project source code and install it -# Installing separately from its dependencies allows optimal layer caching -ADD . /app -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev --no-editable - -FROM python:3.12-slim-bookworm - -WORKDIR /app - -COPY --from=uv /root/.local /root/.local - -RUN if ! id -u app >/dev/null 2>&1; then \ - useradd -rUM -s /usr/sbin/nologin app; \ - fi -COPY --from=uv --chown=app:app /app/.venv /app/.venv - -# Place executables in the environment at the front of the path -ENV PATH="/app/.venv/bin:$PATH" - -# when running the container, add --db-path and a bind mount to the host's db file -ENTRYPOINT ["mcp-server-fetch"] diff --git a/src/fetch/README.md b/src/fetch/README.md index 7e28699..bc8f0f6 100644 --- a/src/fetch/README.md +++ b/src/fetch/README.md @@ -1,16 +1,7 @@ # Fetch MCP Server - - A Model Context Protocol server that provides web content fetching capabilities. This server enables LLMs to retrieve and process content from web pages, converting HTML to markdown for easier consumption. -Source: https://github.com/modelcontextprotocol/servers/tree/main/src/fetch - -Requires MCP Python SDK 1.x (`mcp>=1.29.0,<2`). SDK 2.0 renamed APIs this server uses. The port to v2 is in progress. - -> [!CAUTION] -> This server can access local/internal IP addresses and may represent a security risk. Exercise caution when using this MCP server to ensure this does not expose any sensitive data. - The fetch tool will truncate the response, but by using the `start_index` argument, you can specify where to start the content extraction. This lets models read a webpage in chunks, until they find the information they need. ### Available Tools @@ -61,27 +52,10 @@ Add to your Claude settings: Using uvx ```json -{ - "mcpServers": { - "fetch": { - "command": "uvx", - "args": ["mcp-server-fetch"] - } - } -} -``` -
- -
-Using docker - -```json -{ - "mcpServers": { - "fetch": { - "command": "docker", - "args": ["run", "-i", "--rm", "mcp/fetch"] - } +"mcpServers": { + "fetch": { + "command": "uvx", + "args": ["mcp-server-fetch"] } } ``` @@ -91,60 +65,10 @@ Add to your Claude settings: Using pip installation ```json -{ - "mcpServers": { - "fetch": { - "command": "python", - "args": ["-m", "mcp_server_fetch"] - } - } -} -``` -
- -### Configure for VS Code - -For quick installation, use one of the one-click install buttons below... - -[![Install with UV in VS Code](https://img.shields.io/badge/VS_Code-UV-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=fetch&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-fetch%22%5D%7D) [![Install with UV in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-UV-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=fetch&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-fetch%22%5D%7D&quality=insiders) - -[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=fetch&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22mcp%2Ffetch%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=fetch&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22mcp%2Ffetch%22%5D%7D&quality=insiders) - -For manual installation, add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`. - -Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. - -> Note that the `mcp` key is needed when using the `mcp.json` file. - -
-Using uvx - -```json -{ - "mcp": { - "servers": { - "fetch": { - "command": "uvx", - "args": ["mcp-server-fetch"] - } - } - } -} -``` -
- -
-Using Docker - -```json -{ - "mcp": { - "servers": { - "fetch": { - "command": "docker", - "args": ["run", "-i", "--rm", "mcp/fetch"] - } - } +"mcpServers": { + "fetch": { + "command": "python", + "args": ["-m", "mcp_server_fetch"] } } ``` @@ -170,52 +94,6 @@ ModelContextProtocol/1.0 (User-Specified; +https://github.com/modelcontextprotoc This can be customized by adding the argument `--user-agent=YourUserAgent` to the `args` list in the configuration. -### Customization - Proxy - -The server can be configured to use a proxy by using the `--proxy-url` argument. - -## Windows Configuration - -If you're experiencing timeout issues on Windows, you may need to set the `PYTHONIOENCODING` environment variable to ensure proper character encoding: - -
-Windows configuration (uvx) - -```json -{ - "mcpServers": { - "fetch": { - "command": "uvx", - "args": ["mcp-server-fetch"], - "env": { - "PYTHONIOENCODING": "utf-8" - } - } - } -} -``` -
- -
-Windows configuration (pip) - -```json -{ - "mcpServers": { - "fetch": { - "command": "python", - "args": ["-m", "mcp_server_fetch"], - "env": { - "PYTHONIOENCODING": "utf-8" - } - } - } -} -``` -
- -This addresses character encoding issues that can cause the server to timeout on Windows systems. - ## Debugging You can use the MCP inspector to debug the server. For uvx installations: diff --git a/src/fetch/pyproject.toml b/src/fetch/pyproject.toml index ee97fc8..54a64ee 100644 --- a/src/fetch/pyproject.toml +++ b/src/fetch/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-server-fetch" -version = "0.6.3" +version = "0.6.1" description = "A Model Context Protocol server providing tools to fetch and convert web content for usage by LLMs" readme = "README.md" requires-python = ">=3.10" @@ -16,19 +16,14 @@ classifiers = [ "Programming Language :: Python :: 3.10", ] dependencies = [ - "httpx>=0.27", "markdownify>=0.13.1", - "mcp>=1.29.0,<2", + "mcp>=1.0.0", "protego>=0.3.1", "pydantic>=2.0.0", "readabilipy>=0.2.0", "requests>=2.32.3", ] -[project.urls] -Repository = "https://github.com/modelcontextprotocol/servers/tree/main/src/fetch" -Issues = "https://github.com/modelcontextprotocol/servers/issues" - [project.scripts] mcp-server-fetch = "mcp_server_fetch:main" @@ -37,8 +32,4 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.uv] -dev-dependencies = ["pyright>=1.1.389", "ruff>=0.7.3", "pytest>=8.0.0", "pytest-asyncio>=0.21.0"] - -[tool.pytest.ini_options] -testpaths = ["tests"] -asyncio_mode = "auto" +dev-dependencies = ["pyright>=1.1.389", "ruff>=0.7.3"] diff --git a/src/fetch/src/mcp_server_fetch/__init__.py b/src/fetch/src/mcp_server_fetch/__init__.py index 09744ce..e3a35d5 100644 --- a/src/fetch/src/mcp_server_fetch/__init__.py +++ b/src/fetch/src/mcp_server_fetch/__init__.py @@ -15,10 +15,9 @@ def main(): action="store_true", help="Ignore robots.txt restrictions", ) - parser.add_argument("--proxy-url", type=str, help="Proxy URL to use for requests") args = parser.parse_args() - asyncio.run(serve(args.user_agent, args.ignore_robots_txt, args.proxy_url)) + asyncio.run(serve(args.user_agent, args.ignore_robots_txt)) if __name__ == "__main__": diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index b42c7b1..3d35094 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -7,7 +7,6 @@ from mcp.shared.exceptions import McpError from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import ( - ErrorData, GetPromptResult, Prompt, PromptArgument, @@ -45,7 +44,7 @@ def extract_content_from_html(html: str) -> str: return content -def get_robots_txt_url(url: str) -> str: +def get_robots_txt_url(url: AnyUrl | str) -> str: """Get the robots.txt URL for a given website URL. Args: @@ -55,7 +54,7 @@ def get_robots_txt_url(url: str) -> str: URL of the robots.txt file """ # Parse the URL into components - parsed = urlparse(url) + parsed = urlparse(str(url)) # Reconstruct the base URL with just scheme, netloc, and /robots.txt path robots_url = urlunparse((parsed.scheme, parsed.netloc, "/robots.txt", "", "", "")) @@ -63,7 +62,7 @@ def get_robots_txt_url(url: str) -> str: return robots_url -async def check_may_autonomously_fetch_url(url: str, user_agent: str, proxy_url: str | None = None) -> None: +async def check_may_autonomously_fetch_url(url: AnyUrl | str, user_agent: str) -> None: """ Check if the URL can be fetched by the user agent according to the robots.txt file. Raises a McpError if not. @@ -72,23 +71,21 @@ async def check_may_autonomously_fetch_url(url: str, user_agent: str, proxy_url: robot_txt_url = get_robots_txt_url(url) - async with AsyncClient(proxy=proxy_url) as client: + async with AsyncClient() as client: try: response = await client.get( - robot_txt_url, - follow_redirects=True, - headers={"User-Agent": user_agent}, + robot_txt_url, headers={"User-Agent": user_agent} ) except HTTPError: - raise McpError(ErrorData( - code=INTERNAL_ERROR, - message=f"Failed to fetch robots.txt {robot_txt_url} due to a connection issue", - )) + raise McpError( + INTERNAL_ERROR, + f"Failed to fetch robots.txt {robot_txt_url} due to a connection issue", + ) if response.status_code in (401, 403): - raise McpError(ErrorData( - code=INTERNAL_ERROR, - message=f"When fetching robots.txt ({robot_txt_url}), received status {response.status_code} so assuming that autonomous fetching is not allowed, the user can try manually fetching by using the fetch prompt", - )) + raise McpError( + INTERNAL_ERROR, + f"When fetching robots.txt ({robot_txt_url}), received status {response.status_code} so assuming that autonomous fetching is not allowed, the user can try manually fetching by using the fetch prompt", + ) elif 400 <= response.status_code < 500: return robot_txt = response.text @@ -96,41 +93,41 @@ async def check_may_autonomously_fetch_url(url: str, user_agent: str, proxy_url: line for line in robot_txt.splitlines() if not line.strip().startswith("#") ) robot_parser = Protego.parse(processed_robot_txt) - if not robot_parser.can_fetch(str(url), user_agent): - raise McpError(ErrorData( - code=INTERNAL_ERROR, - message=f"The sites robots.txt ({robot_txt_url}), specifies that autonomous fetching of this page is not allowed, " + if not robot_parser.can_fetch(url, user_agent): + raise McpError( + INTERNAL_ERROR, + f"The sites robots.txt ({robot_txt_url}), specifies that autonomous fetching of this page is not allowed, " f"{user_agent}\n" f"{url}" f"\n{robot_txt}\n\n" f"The assistant must let the user know that it failed to view the page. The assistant may provide further guidance based on the above information.\n" f"The assistant can tell the user that they can try manually fetching the page by using the fetch prompt within their UI.", - )) + ) async def fetch_url( - url: str, user_agent: str, force_raw: bool = False, proxy_url: str | None = None + url: AnyUrl | str, user_agent: str, force_raw: bool = False ) -> Tuple[str, str]: """ Fetch the URL and return the content in a form ready for the LLM, as well as a prefix string with status information. """ from httpx import AsyncClient, HTTPError - async with AsyncClient(proxy=proxy_url) as client: + async with AsyncClient() as client: try: response = await client.get( - url, + str(url), follow_redirects=True, headers={"User-Agent": user_agent}, timeout=30, ) except HTTPError as e: - raise McpError(ErrorData(code=INTERNAL_ERROR, message=f"Failed to fetch {url}: {e!r}")) + raise McpError(INTERNAL_ERROR, f"Failed to fetch {url}: {e!r}") if response.status_code >= 400: - raise McpError(ErrorData( - code=INTERNAL_ERROR, - message=f"Failed to fetch {url} - status code {response.status_code}", - )) + raise McpError( + INTERNAL_ERROR, + f"Failed to fetch {url} - status code {response.status_code}", + ) page_raw = response.text @@ -173,22 +170,19 @@ class Fetch(BaseModel): bool, Field( default=False, - description="Get the actual HTML content of the requested page, without simplification.", + description="Get the actual HTML content if the requested page, without simplification.", ), ] async def serve( - custom_user_agent: str | None = None, - ignore_robots_txt: bool = False, - proxy_url: str | None = None, + custom_user_agent: str | None = None, ignore_robots_txt: bool = False ) -> None: """Run the fetch MCP server. Args: custom_user_agent: Optional custom User-Agent string to use for requests ignore_robots_txt: Whether to ignore robots.txt restrictions - proxy_url: Optional proxy URL to use for requests """ server = Server("mcp-fetch") user_agent_autonomous = custom_user_agent or DEFAULT_USER_AGENT_AUTONOMOUS @@ -225,44 +219,32 @@ Although originally you did not have internet access, and were advised to refuse try: args = Fetch(**arguments) except ValueError as e: - raise McpError(ErrorData(code=INVALID_PARAMS, message=str(e))) + raise McpError(INVALID_PARAMS, str(e)) - url = str(args.url) + url = args.url if not url: - raise McpError(ErrorData(code=INVALID_PARAMS, message="URL is required")) + raise McpError(INVALID_PARAMS, "URL is required") if not ignore_robots_txt: - await check_may_autonomously_fetch_url(url, user_agent_autonomous, proxy_url) + await check_may_autonomously_fetch_url(url, user_agent_autonomous) content, prefix = await fetch_url( - url, user_agent_autonomous, force_raw=args.raw, proxy_url=proxy_url + url, user_agent_autonomous, force_raw=args.raw ) - original_length = len(content) - if args.start_index >= original_length: - content = "No more content available." - else: - truncated_content = content[args.start_index : args.start_index + args.max_length] - if not truncated_content: - content = "No more content available." - else: - content = truncated_content - actual_content_length = len(truncated_content) - remaining_content = original_length - (args.start_index + actual_content_length) - # Only add the prompt to continue fetching if there is still remaining content - if actual_content_length == args.max_length and remaining_content > 0: - next_start = args.start_index + actual_content_length - content += f"\n\nContent truncated. Call the fetch tool with a start_index of {next_start} to get more content." + if len(content) > args.max_length: + content = content[args.start_index : args.start_index + args.max_length] + content += f"\n\nContent truncated. Call the fetch tool with a start_index of {args.start_index + args.max_length} to get more content." return [TextContent(type="text", text=f"{prefix}Contents of {url}:\n{content}")] @server.get_prompt() async def get_prompt(name: str, arguments: dict | None) -> GetPromptResult: if not arguments or "url" not in arguments: - raise McpError(ErrorData(code=INVALID_PARAMS, message="URL is required")) + raise McpError(INVALID_PARAMS, "URL is required") url = arguments["url"] try: - content, prefix = await fetch_url(url, user_agent_manual, proxy_url=proxy_url) + content, prefix = await fetch_url(url, user_agent_manual) # TODO: after SDK bug is addressed, don't catch the exception except McpError as e: return GetPromptResult( @@ -285,4 +267,4 @@ Although originally you did not have internet access, and were advised to refuse options = server.create_initialization_options() async with stdio_server() as (read_stream, write_stream): - await server.run(read_stream, write_stream, options, raise_exceptions=False) + await server.run(read_stream, write_stream, options, raise_exceptions=True) diff --git a/src/fetch/tests/__init__.py b/src/fetch/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/fetch/tests/test_server.py b/src/fetch/tests/test_server.py deleted file mode 100644 index 96c1cb3..0000000 --- a/src/fetch/tests/test_server.py +++ /dev/null @@ -1,326 +0,0 @@ -"""Tests for the fetch MCP server.""" - -import pytest -from unittest.mock import AsyncMock, patch, MagicMock -from mcp.shared.exceptions import McpError - -from mcp_server_fetch.server import ( - extract_content_from_html, - get_robots_txt_url, - check_may_autonomously_fetch_url, - fetch_url, - DEFAULT_USER_AGENT_AUTONOMOUS, -) - - -class TestGetRobotsTxtUrl: - """Tests for get_robots_txt_url function.""" - - def test_simple_url(self): - """Test with a simple URL.""" - result = get_robots_txt_url("https://example.com/page") - assert result == "https://example.com/robots.txt" - - def test_url_with_path(self): - """Test with URL containing path.""" - result = get_robots_txt_url("https://example.com/some/deep/path/page.html") - assert result == "https://example.com/robots.txt" - - def test_url_with_query_params(self): - """Test with URL containing query parameters.""" - result = get_robots_txt_url("https://example.com/page?foo=bar&baz=qux") - assert result == "https://example.com/robots.txt" - - def test_url_with_port(self): - """Test with URL containing port number.""" - result = get_robots_txt_url("https://example.com:8080/page") - assert result == "https://example.com:8080/robots.txt" - - def test_url_with_fragment(self): - """Test with URL containing fragment.""" - result = get_robots_txt_url("https://example.com/page#section") - assert result == "https://example.com/robots.txt" - - def test_http_url(self): - """Test with HTTP URL.""" - result = get_robots_txt_url("http://example.com/page") - assert result == "http://example.com/robots.txt" - - -class TestExtractContentFromHtml: - """Tests for extract_content_from_html function.""" - - def test_simple_html(self): - """Test with simple HTML content.""" - html = """ - - Test Page - -
-

Hello World

-

This is a test paragraph.

-
- - - """ - result = extract_content_from_html(html) - # readabilipy may extract different parts depending on the content - assert "test paragraph" in result - - def test_html_with_links(self): - """Test that links are converted to markdown.""" - html = """ - - -
-

Visit Example for more.

-
- - - """ - result = extract_content_from_html(html) - assert "Example" in result - - def test_empty_content_returns_error(self): - """Test that empty/invalid HTML returns error message.""" - html = "" - result = extract_content_from_html(html) - assert "" in result - - -class TestCheckMayAutonomouslyFetchUrl: - """Tests for check_may_autonomously_fetch_url function.""" - - @pytest.mark.asyncio - async def test_allows_when_robots_txt_404(self): - """Test that fetching is allowed when robots.txt returns 404.""" - mock_response = MagicMock() - mock_response.status_code = 404 - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - # Should not raise - await check_may_autonomously_fetch_url( - "https://example.com/page", - DEFAULT_USER_AGENT_AUTONOMOUS - ) - - @pytest.mark.asyncio - async def test_blocks_when_robots_txt_401(self): - """Test that fetching is blocked when robots.txt returns 401.""" - mock_response = MagicMock() - mock_response.status_code = 401 - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - with pytest.raises(McpError): - await check_may_autonomously_fetch_url( - "https://example.com/page", - DEFAULT_USER_AGENT_AUTONOMOUS - ) - - @pytest.mark.asyncio - async def test_blocks_when_robots_txt_403(self): - """Test that fetching is blocked when robots.txt returns 403.""" - mock_response = MagicMock() - mock_response.status_code = 403 - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - with pytest.raises(McpError): - await check_may_autonomously_fetch_url( - "https://example.com/page", - DEFAULT_USER_AGENT_AUTONOMOUS - ) - - @pytest.mark.asyncio - async def test_allows_when_robots_txt_allows_all(self): - """Test that fetching is allowed when robots.txt allows all.""" - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = "User-agent: *\nAllow: /" - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - # Should not raise - await check_may_autonomously_fetch_url( - "https://example.com/page", - DEFAULT_USER_AGENT_AUTONOMOUS - ) - - @pytest.mark.asyncio - async def test_blocks_when_robots_txt_disallows_all(self): - """Test that fetching is blocked when robots.txt disallows all.""" - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = "User-agent: *\nDisallow: /" - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - with pytest.raises(McpError): - await check_may_autonomously_fetch_url( - "https://example.com/page", - DEFAULT_USER_AGENT_AUTONOMOUS - ) - - -class TestFetchUrl: - """Tests for fetch_url function.""" - - @pytest.mark.asyncio - async def test_fetch_html_page(self): - """Test fetching an HTML page returns markdown content.""" - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = """ - - -
-

Test Page

-

Hello World

-
- - - """ - mock_response.headers = {"content-type": "text/html"} - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - content, prefix = await fetch_url( - "https://example.com/page", - DEFAULT_USER_AGENT_AUTONOMOUS - ) - - # HTML is processed, so we check it returns something - assert isinstance(content, str) - assert prefix == "" - - @pytest.mark.asyncio - async def test_fetch_html_page_raw(self): - """Test fetching an HTML page with raw=True returns original HTML.""" - html_content = "

Test

" - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = html_content - mock_response.headers = {"content-type": "text/html"} - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - content, prefix = await fetch_url( - "https://example.com/page", - DEFAULT_USER_AGENT_AUTONOMOUS, - force_raw=True - ) - - assert content == html_content - assert "cannot be simplified" in prefix - - @pytest.mark.asyncio - async def test_fetch_json_returns_raw(self): - """Test fetching JSON content returns raw content.""" - json_content = '{"key": "value"}' - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = json_content - mock_response.headers = {"content-type": "application/json"} - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - content, prefix = await fetch_url( - "https://api.example.com/data", - DEFAULT_USER_AGENT_AUTONOMOUS - ) - - assert content == json_content - assert "cannot be simplified" in prefix - - @pytest.mark.asyncio - async def test_fetch_404_raises_error(self): - """Test that 404 response raises McpError.""" - mock_response = MagicMock() - mock_response.status_code = 404 - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - with pytest.raises(McpError): - await fetch_url( - "https://example.com/notfound", - DEFAULT_USER_AGENT_AUTONOMOUS - ) - - @pytest.mark.asyncio - async def test_fetch_500_raises_error(self): - """Test that 500 response raises McpError.""" - mock_response = MagicMock() - mock_response.status_code = 500 - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - with pytest.raises(McpError): - await fetch_url( - "https://example.com/error", - DEFAULT_USER_AGENT_AUTONOMOUS - ) - - @pytest.mark.asyncio - async def test_fetch_with_proxy(self): - """Test that proxy URL is passed to client.""" - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.text = '{"data": "test"}' - mock_response.headers = {"content-type": "application/json"} - - with patch("httpx.AsyncClient") as mock_client_class: - mock_client = AsyncMock() - mock_client.get = AsyncMock(return_value=mock_response) - mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) - mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - - await fetch_url( - "https://example.com/data", - DEFAULT_USER_AGENT_AUTONOMOUS, - proxy_url="http://proxy.example.com:8080" - ) - - # Verify AsyncClient was called with proxy - mock_client_class.assert_called_once_with(proxy="http://proxy.example.com:8080") diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index f35a318..397a1b5 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -1,20 +1,13 @@ version = 1 -revision = 3 requires-python = ">=3.10" -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform != 'win32'", - "python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.14' and sys_platform != 'win32'", -] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] [[package]] @@ -27,27 +20,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422, upload-time = "2024-10-14T14:31:44.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377, upload-time = "2024-10-14T14:31:42.623Z" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, ] [[package]] @@ -57,161 +32,87 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181, upload-time = "2024-01-17T16:53:17.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925, upload-time = "2024-01-17T16:53:12.779Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, ] [[package]] name = "certifi" version = "2024.8.30" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507, upload-time = "2024-08-30T01:55:04.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321, upload-time = "2024-08-30T01:55:02.591Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, ] [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013, upload-time = "2024-12-24T18:09:43.671Z" }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285, upload-time = "2024-12-24T18:09:48.113Z" }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449, upload-time = "2024-12-24T18:09:50.845Z" }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892, upload-time = "2024-12-24T18:09:52.078Z" }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123, upload-time = "2024-12-24T18:09:54.575Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943, upload-time = "2024-12-24T18:09:57.324Z" }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063, upload-time = "2024-12-24T18:09:59.794Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578, upload-time = "2024-12-24T18:10:02.357Z" }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629, upload-time = "2024-12-24T18:10:03.678Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778, upload-time = "2024-12-24T18:10:06.197Z" }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453, upload-time = "2024-12-24T18:10:08.848Z" }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479, upload-time = "2024-12-24T18:10:10.044Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790, upload-time = "2024-12-24T18:10:11.323Z" }, - { 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, upload-time = "2024-12-24T18:10:12.838Z" }, - { 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, upload-time = "2024-12-24T18:10:14.101Z" }, - { 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, upload-time = "2024-12-24T18:10:15.512Z" }, - { 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, upload-time = "2024-12-24T18:10:18.369Z" }, - { 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, upload-time = "2024-12-24T18:10:19.743Z" }, - { 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, upload-time = "2024-12-24T18:10:21.139Z" }, - { 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, upload-time = "2024-12-24T18:10:22.382Z" }, - { 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, upload-time = "2024-12-24T18:10:24.802Z" }, - { 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, upload-time = "2024-12-24T18:10:26.124Z" }, - { 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, upload-time = "2024-12-24T18:10:30.027Z" }, - { 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, upload-time = "2024-12-24T18:10:32.679Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205, upload-time = "2024-12-24T18:10:34.724Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441, upload-time = "2024-12-24T18:10:37.574Z" }, - { 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, upload-time = "2024-12-24T18:10:38.83Z" }, - { 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, upload-time = "2024-12-24T18:10:44.272Z" }, - { 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, upload-time = "2024-12-24T18:10:45.492Z" }, - { 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, upload-time = "2024-12-24T18:10:47.898Z" }, - { 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, upload-time = "2024-12-24T18:10:50.589Z" }, - { 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, upload-time = "2024-12-24T18:10:52.541Z" }, - { 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, upload-time = "2024-12-24T18:10:53.789Z" }, - { 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, upload-time = "2024-12-24T18:10:55.048Z" }, - { 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, upload-time = "2024-12-24T18:10:57.647Z" }, - { 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, upload-time = "2024-12-24T18:10:59.43Z" }, - { 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, upload-time = "2024-12-24T18:11:00.676Z" }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload-time = "2024-12-24T18:11:01.952Z" }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload-time = "2024-12-24T18:11:03.142Z" }, - { 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, upload-time = "2024-12-24T18:11:05.834Z" }, - { 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, upload-time = "2024-12-24T18:11:07.064Z" }, - { 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, upload-time = "2024-12-24T18:11:08.374Z" }, - { 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, upload-time = "2024-12-24T18:11:09.831Z" }, - { 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, upload-time = "2024-12-24T18:11:12.03Z" }, - { 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, upload-time = "2024-12-24T18:11:13.372Z" }, - { 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, upload-time = "2024-12-24T18:11:14.628Z" }, - { 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, upload-time = "2024-12-24T18:11:17.672Z" }, - { 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, upload-time = "2024-12-24T18:11:18.989Z" }, - { 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, upload-time = "2024-12-24T18:11:21.507Z" }, - { 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, upload-time = "2024-12-24T18:11:22.774Z" }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, + { url = "https://files.pythonhosted.org/packages/69/8b/825cc84cf13a28bfbcba7c416ec22bf85a9584971be15b21dd8300c65b7f/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", size = 196363 }, + { url = "https://files.pythonhosted.org/packages/23/81/d7eef6a99e42c77f444fdd7bc894b0ceca6c3a95c51239e74a722039521c/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", size = 125639 }, + { url = "https://files.pythonhosted.org/packages/21/67/b4564d81f48042f520c948abac7079356e94b30cb8ffb22e747532cf469d/charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", size = 120451 }, + { url = "https://files.pythonhosted.org/packages/c2/72/12a7f0943dd71fb5b4e7b55c41327ac0a1663046a868ee4d0d8e9c369b85/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", size = 140041 }, + { url = "https://files.pythonhosted.org/packages/67/56/fa28c2c3e31217c4c52158537a2cf5d98a6c1e89d31faf476c89391cd16b/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", size = 150333 }, + { url = "https://files.pythonhosted.org/packages/f9/d2/466a9be1f32d89eb1554cf84073a5ed9262047acee1ab39cbaefc19635d2/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", size = 142921 }, + { url = "https://files.pythonhosted.org/packages/f8/01/344ec40cf5d85c1da3c1f57566c59e0c9b56bcc5566c08804a95a6cc8257/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", size = 144785 }, + { url = "https://files.pythonhosted.org/packages/73/8b/2102692cb6d7e9f03b9a33a710e0164cadfce312872e3efc7cfe22ed26b4/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", size = 146631 }, + { url = "https://files.pythonhosted.org/packages/d8/96/cc2c1b5d994119ce9f088a9a0c3ebd489d360a2eb058e2c8049f27092847/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", size = 140867 }, + { url = "https://files.pythonhosted.org/packages/c9/27/cde291783715b8ec30a61c810d0120411844bc4c23b50189b81188b273db/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", size = 149273 }, + { url = "https://files.pythonhosted.org/packages/3a/a4/8633b0fc1a2d1834d5393dafecce4a1cc56727bfd82b4dc18fc92f0d3cc3/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", size = 152437 }, + { url = "https://files.pythonhosted.org/packages/64/ea/69af161062166b5975ccbb0961fd2384853190c70786f288684490913bf5/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", size = 150087 }, + { url = "https://files.pythonhosted.org/packages/3b/fd/e60a9d9fd967f4ad5a92810138192f825d77b4fa2a557990fd575a47695b/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", size = 145142 }, + { url = "https://files.pythonhosted.org/packages/6d/02/8cb0988a1e49ac9ce2eed1e07b77ff118f2923e9ebd0ede41ba85f2dcb04/charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", size = 94701 }, + { url = "https://files.pythonhosted.org/packages/d6/20/f1d4670a8a723c46be695dff449d86d6092916f9e99c53051954ee33a1bc/charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", size = 102191 }, + { url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 }, + { url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 }, + { url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 }, + { url = "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", size = 138243 }, + { url = "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", size = 148676 }, + { url = "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", size = 141289 }, + { url = "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", size = 142585 }, + { url = "https://files.pythonhosted.org/packages/3b/a0/a68980ab8a1f45a36d9745d35049c1af57d27255eff8c907e3add84cf68f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", size = 144408 }, + { url = "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", size = 139076 }, + { url = "https://files.pythonhosted.org/packages/fb/9d/9c13753a5a6e0db4a0a6edb1cef7aee39859177b64e1a1e748a6e3ba62c2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", size = 146874 }, + { url = "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", size = 150871 }, + { url = "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", size = 148546 }, + { url = "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", size = 143048 }, + { url = "https://files.pythonhosted.org/packages/01/f8/38842422988b795220eb8038745d27a675ce066e2ada79516c118f291f07/charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", size = 94389 }, + { url = "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", size = 101752 }, + { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, + { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, + { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, + { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, + { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, + { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, + { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, + { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, + { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, + { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, + { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, + { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, + { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, + { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, + { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, + { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, + { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, + { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, + { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, + { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, + { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, + { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, + { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, + { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, + { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, + { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, ] [[package]] @@ -219,95 +120,38 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "platform_system == 'Windows'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cryptography" -version = "49.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, - { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, - { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, - { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, - { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, - { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, - { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, - { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, - { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, - { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, - { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, - { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, - { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, - { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, - { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, - { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, - { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, - { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, - { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, - { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, - { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] [[package]] name = "h11" -version = "0.16.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, ] [[package]] @@ -318,210 +162,137 @@ dependencies = [ { name = "six" }, { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, + { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173 }, ] [[package]] name = "httpcore" -version = "1.0.9" +version = "1.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, ] [[package]] name = "httpx" -version = "0.27.2" +version = "0.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "certifi" }, { name = "httpcore" }, { name = "idna" }, - { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189, upload-time = "2024-08-27T12:54:01.334Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/df/676b7cf674dd1bdc71a64ad393c89879f75e4a0ab8395165b498262ae106/httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0", size = 141307 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395, upload-time = "2024-08-27T12:53:59.653Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fb/a19866137577ba60c6d8b69498dc36be479b13ba454f691348ddf428f185/httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc", size = 73551 }, ] [[package]] name = "httpx-sse" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, ] [[package]] name = "idna" -version = "3.18" +version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "jsonschema" -version = "4.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] [[package]] name = "lxml" -version = "6.1.0" +version = "5.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/6e/ee8fc0e01202eb3dd2b9e1ea4f0910d72425d35c66187c63931d7a3ea73f/lxml-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec", size = 8540733, upload-time = "2026-04-18T04:27:33.185Z" }, - { url = "https://files.pythonhosted.org/packages/54/e8/325fe9b942824c773dffe1baf0c35b046a763851fdff4393af4450bceeb7/lxml-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec", size = 4602805, upload-time = "2026-04-18T04:27:36.097Z" }, - { url = "https://files.pythonhosted.org/packages/2d/81/221aa3ea4a40370bb0358fa454cbe7e5a837e522f7630c24dfef3f9a73b0/lxml-6.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551", size = 5002652, upload-time = "2026-04-18T04:27:30.603Z" }, - { url = "https://files.pythonhosted.org/packages/c6/e1/fdbfb9019542f1875c093576df7f37adc2983c8ba7ecf17e5f14490bc107/lxml-6.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd", size = 5155332, upload-time = "2026-04-18T04:27:33.507Z" }, - { url = "https://files.pythonhosted.org/packages/56/b1/4087c782fff397cd03abf9c551069be59bb04a7e548c50fb7b9c4cdaca28/lxml-6.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215", size = 5057226, upload-time = "2026-04-18T04:27:37.567Z" }, - { url = "https://files.pythonhosted.org/packages/5d/66/516c79dec8417f3a972327330254c0b5fac93d5c3ecfd8a5b43650a5a4d9/lxml-6.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4", size = 5287588, upload-time = "2026-04-18T04:27:41.4Z" }, - { url = "https://files.pythonhosted.org/packages/94/1d/e578f4cbeb42b9df9f29b0d44a45a7cdfa3a5ae300dd59ec68e3602d29bb/lxml-6.1.0-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea", size = 5412438, upload-time = "2026-04-18T04:27:45.589Z" }, - { url = "https://files.pythonhosted.org/packages/47/5b/2aa68307d6d15959e84d4882f9c04f2da63127eac463e1594166f681ef77/lxml-6.1.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6", size = 4770997, upload-time = "2026-04-18T04:27:49.853Z" }, - { url = "https://files.pythonhosted.org/packages/ae/c9/3e51fc1228310a836b4eb32595ae00154ab12197fca944676a3ab3b163ea/lxml-6.1.0-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3", size = 5359678, upload-time = "2026-04-18T04:31:56.184Z" }, - { url = "https://files.pythonhosted.org/packages/b5/91/ab8bc834f977fbbd310e697b120787c153db026f9151e02a88d2645d4e5b/lxml-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7", size = 5107890, upload-time = "2026-04-18T04:32:00.387Z" }, - { url = "https://files.pythonhosted.org/packages/bb/10/8a143cfa3ac99cb5b0523ff6d0429a9c9dddf25ffeae09caa3866c7964d9/lxml-6.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16", size = 4803977, upload-time = "2026-04-18T04:32:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/45/fd/ee02faf52fa39c2fe32f824628958b9aa86dff21343dc3161f0e3c6ccd15/lxml-6.1.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1", size = 5350277, upload-time = "2026-04-18T04:32:09.176Z" }, - { url = "https://files.pythonhosted.org/packages/85/8c/b3481364b8554b5d36d540189a87fc71e94b0b01c24f8f152bd662dd2e45/lxml-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a", size = 5309717, upload-time = "2026-04-18T04:32:13.303Z" }, - { url = "https://files.pythonhosted.org/packages/74/e8/a6b21927077a9127afa17473b6576b322616f34ac50ee4f577e763b75ec0/lxml-6.1.0-cp310-cp310-win32.whl", hash = "sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544", size = 3598491, upload-time = "2026-04-18T04:27:24.288Z" }, - { url = "https://files.pythonhosted.org/packages/ea/82/14dea800d041274d96c07d49ff9191f011d1427450850de19bf541e2cc12/lxml-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a", size = 4020906, upload-time = "2026-04-18T04:27:27.53Z" }, - { url = "https://files.pythonhosted.org/packages/f2/ba/d3539aaf4d9d21456b9a7b902816623227d05d63e7c5aafd8834c4b9bed6/lxml-6.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776", size = 3667787, upload-time = "2026-04-18T04:27:29.407Z" }, - { url = "https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9", size = 8526232, upload-time = "2026-04-18T04:27:40.389Z" }, - { url = "https://files.pythonhosted.org/packages/a7/51/adc8826570a112f83bb4ddb3a2ab510bbc2ccd62c1b9fe1f34fae2d90b57/lxml-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50", size = 4595448, upload-time = "2026-04-18T04:27:44.208Z" }, - { url = "https://files.pythonhosted.org/packages/54/84/5a9ec07cbe1d2334a6465f863b949a520d2699a755738986dcd3b6b89e3f/lxml-6.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5", size = 4923771, upload-time = "2026-04-18T04:32:17.402Z" }, - { url = "https://files.pythonhosted.org/packages/a7/23/851cfa33b6b38adb628e45ad51fb27105fa34b2b3ba9d1d4aa7a9428dfe0/lxml-6.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e", size = 5068101, upload-time = "2026-04-18T04:32:21.437Z" }, - { url = "https://files.pythonhosted.org/packages/b0/38/41bf99c2023c6b79916ba057d83e9db21d642f473cac210201222882d38b/lxml-6.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512", size = 5002573, upload-time = "2026-04-18T04:32:25.373Z" }, - { url = "https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c", size = 5202816, upload-time = "2026-04-18T04:32:29.393Z" }, - { url = "https://files.pythonhosted.org/packages/9a/da/bc710fad8bf04b93baee752c192eaa2210cd3a84f969d0be7830fea55802/lxml-6.1.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5", size = 5329999, upload-time = "2026-04-18T04:32:34.019Z" }, - { url = "https://files.pythonhosted.org/packages/b3/cb/bf035dedbdf7fab49411aa52e4236f3445e98d38647d85419e6c0d2806b9/lxml-6.1.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289", size = 4659643, upload-time = "2026-04-18T04:32:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4f/22be31f33727a5e4c7b01b0a874503026e50329b259d3587e0b923cf964b/lxml-6.1.0-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a", size = 5265963, upload-time = "2026-04-18T04:32:41.881Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2b/d44d0e5c79226017f4ab8c87a802ebe4f89f97e6585a8e4166dffcdd7b6e/lxml-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3", size = 5045444, upload-time = "2026-04-18T04:32:44.512Z" }, - { url = "https://files.pythonhosted.org/packages/d3/c3/3f034fec1594c331a6dbf9491238fdcc9d66f68cc529e109ec75b97197e1/lxml-6.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9", size = 4712703, upload-time = "2026-04-18T04:32:47.16Z" }, - { url = "https://files.pythonhosted.org/packages/12/16/0b83fccc158218aca75a7aa33e97441df737950734246b9fffa39301603d/lxml-6.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11", size = 5252745, upload-time = "2026-04-18T04:32:50.427Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ee/12e6c1b39a77666c02eaa77f94a870aaf63c4ac3a497b2d52319448b01c6/lxml-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4", size = 5226822, upload-time = "2026-04-18T04:32:53.437Z" }, - { url = "https://files.pythonhosted.org/packages/34/20/c7852904858b4723af01d2fc14b5d38ff57cb92f01934a127ebd9a9e51aa/lxml-6.1.0-cp311-cp311-win32.whl", hash = "sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3", size = 3594026, upload-time = "2026-04-18T04:27:31.903Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7", size = 4025114, upload-time = "2026-04-18T04:27:34.077Z" }, - { url = "https://files.pythonhosted.org/packages/c2/df/c84dcc175fd690823436d15b41cb920cd5ba5e14cd8bfb00949d5903b320/lxml-6.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39", size = 3667742, upload-time = "2026-04-18T04:27:38.45Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, - { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, - { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, - { url = "https://files.pythonhosted.org/packages/e3/38/a0db9be8f38ad6043ab9429487c128dd1d30f07956ef43040402f8da49e8/lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", size = 5630827, upload-time = "2026-04-18T04:33:04.036Z" }, - { url = "https://files.pythonhosted.org/packages/31/ba/3c13d3fc24b7cacf675f808a3a1baabf43a30d0cd24c98f94548e9aa58eb/lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", size = 5240445, upload-time = "2026-04-18T04:33:06.87Z" }, - { url = "https://files.pythonhosted.org/packages/55/ba/eeef4ccba09b2212fe239f46c1692a98db1878e0872ae320756488878a94/lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32", size = 5350121, upload-time = "2026-04-18T04:33:09.365Z" }, - { url = "https://files.pythonhosted.org/packages/7e/01/1da87c7b587c38d0cbe77a01aae3b9c1c49ed47d76918ef3db8fc151b1ca/lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", size = 4694949, upload-time = "2026-04-18T04:33:11.628Z" }, - { url = "https://files.pythonhosted.org/packages/a1/88/7db0fe66d5aaf128443ee1623dec3db1576f3e4c17751ec0ef5866468590/lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", size = 5243901, upload-time = "2026-04-18T04:33:13.95Z" }, - { url = "https://files.pythonhosted.org/packages/00/a8/1346726af7d1f6fca1f11223ba34001462b0a3660416986d37641708d57c/lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", size = 5048054, upload-time = "2026-04-18T04:33:16.965Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b7/85057012f035d1a0c87e02f8c723ca3c3e6e0728bcf4cb62080b21b1c1e3/lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", size = 4777324, upload-time = "2026-04-18T04:33:19.832Z" }, - { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, - { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3a/ac3f99ec8ac93089e7dd556f279e0d14c24de0a74a507e143a2e4b496e7c/lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", size = 3596289, upload-time = "2026-04-18T04:27:42.819Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a7/0a915557538593cb1bbeedcd40e13c7a261822c26fecbbdb71dad0c2f540/lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", size = 3997059, upload-time = "2026-04-18T04:27:46.764Z" }, - { url = "https://files.pythonhosted.org/packages/92/96/a5dc078cf0126fbfbc35611d77ecd5da80054b5893e28fb213a5613b9e1d/lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", size = 3659552, upload-time = "2026-04-18T04:27:51.133Z" }, - { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, - { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, - { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, - { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, - { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, - { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, - { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, - { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, - { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, - { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, - { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, - { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, - { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, - { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" }, - { url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" }, - { url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" }, - { url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" }, - { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, - { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, - { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, - { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, - { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, - { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, - { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, - { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, - { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, - { url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" }, - { url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" }, - { url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" }, - { url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" }, - { url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" }, - { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, - { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, - { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, - { url = "https://files.pythonhosted.org/packages/f2/88/55143966481409b1740a3ac669e611055f49efd68087a5ce41582325db3e/lxml-6.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842", size = 3930134, upload-time = "2026-04-18T04:32:35.008Z" }, - { url = "https://files.pythonhosted.org/packages/b5/97/28b985c2983938d3cb696dd5501423afb90a8c3e869ef5d3c62569282c0f/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c", size = 4210749, upload-time = "2026-04-18T04:36:03.626Z" }, - { url = "https://files.pythonhosted.org/packages/29/67/dfab2b7d58214921935ccea7ce9b3df9b7d46f305d12f0f532ac7cf6b804/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de", size = 4318463, upload-time = "2026-04-18T04:36:06.309Z" }, - { url = "https://files.pythonhosted.org/packages/32/a2/4ac7eb32a4d997dd352c32c32399aae27b3f268d440e6f9cfa405b575d2f/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635", size = 4251124, upload-time = "2026-04-18T04:36:09.056Z" }, - { url = "https://files.pythonhosted.org/packages/33/ef/d6abd850bb4822f9b720cfe36b547a558e694881010ff7d012191e8769c6/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037", size = 4401758, upload-time = "2026-04-18T04:36:11.803Z" }, - { url = "https://files.pythonhosted.org/packages/40/44/3ee09a5b60cb44c4f2fbc1c9015cfd6ff5afc08f991cab295d3024dcbf2d/lxml-6.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace", size = 3508860, upload-time = "2026-04-18T04:32:48.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ce/2789e39eddf2b13fac29878bfa465f0910eb6b0096e29090e5176bc8cf43/lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656", size = 8124570 }, + { url = "https://files.pythonhosted.org/packages/24/a8/f4010166a25d41715527129af2675981a50d3bbf7df09c5d9ab8ca24fbf9/lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d", size = 4413042 }, + { url = "https://files.pythonhosted.org/packages/41/a4/7e45756cecdd7577ddf67a68b69c1db0f5ddbf0c9f65021ee769165ffc5a/lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a", size = 5139213 }, + { url = "https://files.pythonhosted.org/packages/02/e2/ecf845b12323c92748077e1818b64e8b4dba509a4cb12920b3762ebe7552/lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8", size = 4838814 }, + { url = "https://files.pythonhosted.org/packages/12/91/619f9fb72cf75e9ceb8700706f7276f23995f6ad757e6d400fbe35ca4990/lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330", size = 5425084 }, + { url = "https://files.pythonhosted.org/packages/25/3b/162a85a8f0fd2a3032ec3f936636911c6e9523a8e263fffcfd581ce98b54/lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965", size = 4875993 }, + { url = "https://files.pythonhosted.org/packages/43/af/dd3f58cc7d946da6ae42909629a2b1d5dd2d1b583334d4af9396697d6863/lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22", size = 5012462 }, + { url = "https://files.pythonhosted.org/packages/69/c1/5ea46b2d4c98f5bf5c83fffab8a0ad293c9bc74df9ecfbafef10f77f7201/lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b", size = 4815288 }, + { url = "https://files.pythonhosted.org/packages/1d/51/a0acca077ad35da458f4d3f729ef98effd2b90f003440d35fc36323f8ae6/lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7", size = 5472435 }, + { url = "https://files.pythonhosted.org/packages/4d/6b/0989c9368986961a6b0f55b46c80404c4b758417acdb6d87bfc3bd5f4967/lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8", size = 4976354 }, + { url = "https://files.pythonhosted.org/packages/05/9e/87492d03ff604fbf656ed2bf3e2e8d28f5d58ea1f00ff27ac27b06509079/lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32", size = 5029973 }, + { url = "https://files.pythonhosted.org/packages/f9/cc/9ae1baf5472af88e19e2c454b3710c1be9ecafb20eb474eeabcd88a055d2/lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86", size = 4888837 }, + { url = "https://files.pythonhosted.org/packages/d2/10/5594ffaec8c120d75b17e3ad23439b740a51549a9b5fd7484b2179adfe8f/lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5", size = 5530555 }, + { url = "https://files.pythonhosted.org/packages/ea/9b/de17f05377c8833343b629905571fb06cff2028f15a6f58ae2267662e341/lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03", size = 5405314 }, + { url = "https://files.pythonhosted.org/packages/8a/b4/227be0f1f3cca8255925985164c3838b8b36e441ff0cc10c1d3c6bdba031/lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7", size = 5079303 }, + { url = "https://files.pythonhosted.org/packages/5c/ee/19abcebb7fc40319bb71cd6adefa1ad94d09b5660228715854d6cc420713/lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80", size = 3475126 }, + { url = "https://files.pythonhosted.org/packages/a1/35/183d32551447e280032b2331738cd850da435a42f850b71ebeaab42c1313/lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3", size = 3805065 }, + { url = "https://files.pythonhosted.org/packages/5c/a8/449faa2a3cbe6a99f8d38dcd51a3ee8844c17862841a6f769ea7c2a9cd0f/lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b", size = 8141056 }, + { url = "https://files.pythonhosted.org/packages/ac/8a/ae6325e994e2052de92f894363b038351c50ee38749d30cc6b6d96aaf90f/lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18", size = 4425238 }, + { url = "https://files.pythonhosted.org/packages/f8/fb/128dddb7f9086236bce0eeae2bfb316d138b49b159f50bc681d56c1bdd19/lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442", size = 5095197 }, + { url = "https://files.pythonhosted.org/packages/b4/f9/a181a8ef106e41e3086629c8bdb2d21a942f14c84a0e77452c22d6b22091/lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4", size = 4809809 }, + { url = "https://files.pythonhosted.org/packages/25/2f/b20565e808f7f6868aacea48ddcdd7e9e9fb4c799287f21f1a6c7c2e8b71/lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f", size = 5407593 }, + { url = "https://files.pythonhosted.org/packages/23/0e/caac672ec246d3189a16c4d364ed4f7d6bf856c080215382c06764058c08/lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e", size = 4866657 }, + { url = "https://files.pythonhosted.org/packages/67/a4/1f5fbd3f58d4069000522196b0b776a014f3feec1796da03e495cf23532d/lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c", size = 4967017 }, + { url = "https://files.pythonhosted.org/packages/ee/73/623ecea6ca3c530dd0a4ed0d00d9702e0e85cd5624e2d5b93b005fe00abd/lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16", size = 4810730 }, + { url = "https://files.pythonhosted.org/packages/1d/ce/fb84fb8e3c298f3a245ae3ea6221c2426f1bbaa82d10a88787412a498145/lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79", size = 5455154 }, + { url = "https://files.pythonhosted.org/packages/b1/72/4d1ad363748a72c7c0411c28be2b0dc7150d91e823eadad3b91a4514cbea/lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080", size = 4969416 }, + { url = "https://files.pythonhosted.org/packages/42/07/b29571a58a3a80681722ea8ed0ba569211d9bb8531ad49b5cacf6d409185/lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654", size = 5013672 }, + { url = "https://files.pythonhosted.org/packages/b9/93/bde740d5a58cf04cbd38e3dd93ad1e36c2f95553bbf7d57807bc6815d926/lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d", size = 4878644 }, + { url = "https://files.pythonhosted.org/packages/56/b5/645c8c02721d49927c93181de4017164ec0e141413577687c3df8ff0800f/lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763", size = 5511531 }, + { url = "https://files.pythonhosted.org/packages/85/3f/6a99a12d9438316f4fc86ef88c5d4c8fb674247b17f3173ecadd8346b671/lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec", size = 5402065 }, + { url = "https://files.pythonhosted.org/packages/80/8a/df47bff6ad5ac57335bf552babfb2408f9eb680c074ec1ba412a1a6af2c5/lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be", size = 5069775 }, + { url = "https://files.pythonhosted.org/packages/08/ae/e7ad0f0fbe4b6368c5ee1e3ef0c3365098d806d42379c46c1ba2802a52f7/lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9", size = 3474226 }, + { url = "https://files.pythonhosted.org/packages/c3/b5/91c2249bfac02ee514ab135e9304b89d55967be7e53e94a879b74eec7a5c/lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1", size = 3814971 }, + { url = "https://files.pythonhosted.org/packages/eb/6d/d1f1c5e40c64bf62afd7a3f9b34ce18a586a1cccbf71e783cd0a6d8e8971/lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859", size = 8171753 }, + { url = "https://files.pythonhosted.org/packages/bd/83/26b1864921869784355459f374896dcf8b44d4af3b15d7697e9156cb2de9/lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e", size = 4441955 }, + { url = "https://files.pythonhosted.org/packages/e0/d2/e9bff9fb359226c25cda3538f664f54f2804f4b37b0d7c944639e1a51f69/lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f", size = 5050778 }, + { url = "https://files.pythonhosted.org/packages/88/69/6972bfafa8cd3ddc8562b126dd607011e218e17be313a8b1b9cc5a0ee876/lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e", size = 4748628 }, + { url = "https://files.pythonhosted.org/packages/5d/ea/a6523c7c7f6dc755a6eed3d2f6d6646617cad4d3d6d8ce4ed71bfd2362c8/lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179", size = 5322215 }, + { url = "https://files.pythonhosted.org/packages/99/37/396fbd24a70f62b31d988e4500f2068c7f3fd399d2fd45257d13eab51a6f/lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a", size = 4813963 }, + { url = "https://files.pythonhosted.org/packages/09/91/e6136f17459a11ce1757df864b213efbeab7adcb2efa63efb1b846ab6723/lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3", size = 4923353 }, + { url = "https://files.pythonhosted.org/packages/1d/7c/2eeecf87c9a1fca4f84f991067c693e67340f2b7127fc3eca8fa29d75ee3/lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1", size = 4740541 }, + { url = "https://files.pythonhosted.org/packages/3b/ed/4c38ba58defca84f5f0d0ac2480fdcd99fc7ae4b28fc417c93640a6949ae/lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d", size = 5346504 }, + { url = "https://files.pythonhosted.org/packages/a5/22/bbd3995437e5745cb4c2b5d89088d70ab19d4feabf8a27a24cecb9745464/lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c", size = 4898077 }, + { url = "https://files.pythonhosted.org/packages/0a/6e/94537acfb5b8f18235d13186d247bca478fea5e87d224644e0fe907df976/lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99", size = 4946543 }, + { url = "https://files.pythonhosted.org/packages/8d/e8/4b15df533fe8e8d53363b23a41df9be907330e1fa28c7ca36893fad338ee/lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff", size = 4816841 }, + { url = "https://files.pythonhosted.org/packages/1a/e7/03f390ea37d1acda50bc538feb5b2bda6745b25731e4e76ab48fae7106bf/lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a", size = 5417341 }, + { url = "https://files.pythonhosted.org/packages/ea/99/d1133ab4c250da85a883c3b60249d3d3e7c64f24faff494cf0fd23f91e80/lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8", size = 5327539 }, + { url = "https://files.pythonhosted.org/packages/7d/ed/e6276c8d9668028213df01f598f385b05b55a4e1b4662ee12ef05dab35aa/lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d", size = 5012542 }, + { url = "https://files.pythonhosted.org/packages/36/88/684d4e800f5aa28df2a991a6a622783fb73cf0e46235cfa690f9776f032e/lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30", size = 3486454 }, + { url = "https://files.pythonhosted.org/packages/fc/82/ace5a5676051e60355bd8fb945df7b1ba4f4fb8447f2010fb816bfd57724/lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f", size = 3816857 }, + { url = "https://files.pythonhosted.org/packages/94/6a/42141e4d373903bfea6f8e94b2f554d05506dfda522ada5343c651410dc8/lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", size = 8156284 }, + { url = "https://files.pythonhosted.org/packages/91/5e/fa097f0f7d8b3d113fb7312c6308af702f2667f22644441715be961f2c7e/lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", size = 4432407 }, + { url = "https://files.pythonhosted.org/packages/2d/a1/b901988aa6d4ff937f2e5cfc114e4ec561901ff00660c3e56713642728da/lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", size = 5048331 }, + { url = "https://files.pythonhosted.org/packages/30/0f/b2a54f48e52de578b71bbe2a2f8160672a8a5e103df3a78da53907e8c7ed/lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", size = 4744835 }, + { url = "https://files.pythonhosted.org/packages/82/9d/b000c15538b60934589e83826ecbc437a1586488d7c13f8ee5ff1f79a9b8/lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", size = 5316649 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/ffbb9eaff5e541922611d2c56b175c45893d1c0b8b11e5a497708a6a3b3b/lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", size = 4812046 }, + { url = "https://files.pythonhosted.org/packages/15/ff/7ff89d567485c7b943cdac316087f16b2399a8b997007ed352a1248397e5/lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", size = 4918597 }, + { url = "https://files.pythonhosted.org/packages/c6/a3/535b6ed8c048412ff51268bdf4bf1cf052a37aa7e31d2e6518038a883b29/lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", size = 4738071 }, + { url = "https://files.pythonhosted.org/packages/7a/8f/cbbfa59cb4d4fd677fe183725a76d8c956495d7a3c7f111ab8f5e13d2e83/lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", size = 5342213 }, + { url = "https://files.pythonhosted.org/packages/5c/fb/db4c10dd9958d4b52e34d1d1f7c1f434422aeaf6ae2bbaaff2264351d944/lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", size = 4893749 }, + { url = "https://files.pythonhosted.org/packages/f2/38/bb4581c143957c47740de18a3281a0cab7722390a77cc6e610e8ebf2d736/lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", size = 4945901 }, + { url = "https://files.pythonhosted.org/packages/fc/d5/18b7de4960c731e98037bd48fa9f8e6e8f2558e6fbca4303d9b14d21ef3b/lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", size = 4815447 }, + { url = "https://files.pythonhosted.org/packages/97/a8/cd51ceaad6eb849246559a8ef60ae55065a3df550fc5fcd27014361c1bab/lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", size = 5411186 }, + { url = "https://files.pythonhosted.org/packages/89/c3/1e3dabab519481ed7b1fdcba21dcfb8832f57000733ef0e71cf6d09a5e03/lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", size = 5324481 }, + { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 }, + { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 }, + { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 }, + { url = "https://files.pythonhosted.org/packages/99/f7/b73a431c8500565aa500e99e60b448d305eaf7c0b4c893c7c5a8a69cc595/lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c", size = 3925431 }, + { url = "https://files.pythonhosted.org/packages/db/48/4a206623c0d093d0e3b15f415ffb4345b0bdf661a3d0b15a112948c033c7/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a", size = 4216683 }, + { url = "https://files.pythonhosted.org/packages/54/47/577820c45dd954523ae8453b632d91e76da94ca6d9ee40d8c98dd86f916b/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005", size = 4326732 }, + { url = "https://files.pythonhosted.org/packages/68/de/96cb6d3269bc994b4f5ede8ca7bf0840f5de0a278bc6e50cb317ff71cafa/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce", size = 4218377 }, + { url = "https://files.pythonhosted.org/packages/a5/43/19b1ef6cbffa4244a217f95cc5f41a6cb4720fed33510a49670b03c5f1a0/lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83", size = 4351237 }, + { url = "https://files.pythonhosted.org/packages/ba/b2/6a22fb5c0885da3b00e116aee81f0b829ec9ac8f736cd414b4a09413fc7d/lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba", size = 3487557 }, ] [[package]] @@ -532,42 +303,33 @@ dependencies = [ { name = "beautifulsoup4" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/75/483a4bcca436fe88d02dc7686c372631d833848951b368700bdc0c770bb7/markdownify-0.14.1.tar.gz", hash = "sha256:a62a7a216947ed0b8dafb95b99b2ef4a0edd1e18d5653c656f68f03db2bfb2f1", size = 14332, upload-time = "2024-11-24T22:08:30.775Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/75/483a4bcca436fe88d02dc7686c372631d833848951b368700bdc0c770bb7/markdownify-0.14.1.tar.gz", hash = "sha256:a62a7a216947ed0b8dafb95b99b2ef4a0edd1e18d5653c656f68f03db2bfb2f1", size = 14332 } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/0b/74cec93a7b05edf4fc3ea1c899fe8a37f041d7b9d303c75abf7a162924e0/markdownify-0.14.1-py3-none-any.whl", hash = "sha256:4c46a6c0c12c6005ddcd49b45a5a890398b002ef51380cd319db62df5e09bc2a", size = 11530, upload-time = "2024-11-24T22:08:29.005Z" }, + { url = "https://files.pythonhosted.org/packages/65/0b/74cec93a7b05edf4fc3ea1c899fe8a37f041d7b9d303c75abf7a162924e0/markdownify-0.14.1-py3-none-any.whl", hash = "sha256:4c46a6c0c12c6005ddcd49b45a5a890398b002ef51380cd319db62df5e09bc2a", size = 11530 }, ] [[package]] name = "mcp" -version = "1.29.0" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "httpx" }, { name = "httpx-sse" }, - { name = "jsonschema" }, { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "python-multipart" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/d3/f9acc21dfc886e4f78e2add1a47db46ce16884346afde53f8a064c02c891/mcp-1.29.0.tar.gz", hash = "sha256:52d01f334de1868cc3bb2d6604931126a67631f99a6c5d3b82ba47290315ec36", size = 643148, upload-time = "2026-07-28T13:41:41.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/de/a9ec0a1b6439f90ea59f89004bb2e7ec6890dfaeef809751d9e6577dca7e/mcp-1.0.0.tar.gz", hash = "sha256:dba51ce0b5c6a80e25576f606760c49a91ee90210fed805b530ca165d3bbc9b7", size = 82891 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c8/248b201f6d753d69fd5d6506011abbb35a946d9142b2ae311a948fd0be3d/mcp-1.29.0-py3-none-any.whl", hash = "sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7", size = 223436, upload-time = "2026-07-28T13:41:40.337Z" }, + { url = "https://files.pythonhosted.org/packages/56/89/900c0c8445ec001d3725e475fc553b0feb2e8a51be018f3bb7de51e683db/mcp-1.0.0-py3-none-any.whl", hash = "sha256:bbe70ffa3341cd4da78b5eb504958355c68381fb29971471cea1e642a2af5b8a", size = 36361 }, ] [[package]] name = "mcp-server-fetch" -version = "0.6.3" +version = "0.1.3" source = { editable = "." } dependencies = [ - { name = "httpx" }, { name = "markdownify" }, { name = "mcp" }, { name = "protego" }, @@ -579,16 +341,13 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "pyright" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, { name = "ruff" }, ] [package.metadata] requires-dist = [ - { name = "httpx", specifier = ">=0.27" }, { name = "markdownify", specifier = ">=0.13.1" }, - { name = "mcp", specifier = ">=1.29.0,<2" }, + { name = "mcp", specifier = ">=1.0.0" }, { name = "protego", specifier = ">=0.3.1" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "readabilipy", specifier = ">=0.2.0" }, @@ -598,8 +357,6 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "pyright", specifier = ">=1.1.389" }, - { name = "pytest", specifier = ">=8.0.0" }, - { name = "pytest-asyncio", specifier = ">=0.21.0" }, { name = "ruff", specifier = ">=0.7.3" }, ] @@ -607,217 +364,107 @@ dev = [ name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, -] - -[[package]] -name = "packaging" -version = "26.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] [[package]] name = "protego" version = "0.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/12/cab9fa77ff4e9e444a5eb5480db4b4f872c03aa079145804aa054be377bc/Protego-0.3.1.tar.gz", hash = "sha256:e94430d0d25cbbf239bc849d86c5e544fbde531fcccfa059953c7da344a1712c", size = 3246145, upload-time = "2024-04-05T10:08:54.884Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/12/cab9fa77ff4e9e444a5eb5480db4b4f872c03aa079145804aa054be377bc/Protego-0.3.1.tar.gz", hash = "sha256:e94430d0d25cbbf239bc849d86c5e544fbde531fcccfa059953c7da344a1712c", size = 3246145 } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/ef/ece78585a5a189d8cc2b4c2d2b92a0dc025f156a6501159b026472ebbedc/Protego-0.3.1-py2.py3-none-any.whl", hash = "sha256:2fbe8e9b7a7dbc5016a932b14c98d236aad4c29290bbe457b8d2779666ef7a41", size = 8474, upload-time = "2024-04-05T10:08:53.5Z" }, -] - -[[package]] -name = "pycparser" -version = "2.23" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/74/ef/ece78585a5a189d8cc2b4c2d2b92a0dc025f156a6501159b026472ebbedc/Protego-0.3.1-py2.py3-none-any.whl", hash = "sha256:2fbe8e9b7a7dbc5016a932b14c98d236aad4c29290bbe457b8d2779666ef7a41", size = 8474 }, ] [[package]] name = "pydantic" -version = "2.12.5" +version = "2.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, - { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/86/a03390cb12cf64e2a8df07c267f3eb8d5035e0f9a04bb20fb79403d2a00e/pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa", size = 785401 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/d5/74/da832196702d0c56eb86b75bfa346db9238617e29b0b7ee3b8b4eccfe654/pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e", size = 456364 }, ] [[package]] name = "pydantic-core" -version = "2.41.5" +version = "2.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b5/d4/9dfbe238f45ad8b168f5c96ee49a3df0598ce18a0795a983b419949ce65b/pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0", size = 75646, upload-time = "2024-11-01T11:00:05.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f9/ff95fd7d760af42f647ea87f9b8a383d891cdb5e5dbd4613edaeb094252a/pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87", size = 28595, upload-time = "2024-11-01T11:00:02.64Z" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, -] - -[[package]] -name = "pyjwt" -version = "2.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, + { url = "https://files.pythonhosted.org/packages/6e/ce/60fd96895c09738648c83f3f00f595c807cb6735c70d3306b548cc96dd49/pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a", size = 1897984 }, + { url = "https://files.pythonhosted.org/packages/fd/b9/84623d6b6be98cc209b06687d9bca5a7b966ffed008d15225dd0d20cce2e/pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b", size = 1807491 }, + { url = "https://files.pythonhosted.org/packages/01/72/59a70165eabbc93b1111d42df9ca016a4aa109409db04304829377947028/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278", size = 1831953 }, + { url = "https://files.pythonhosted.org/packages/7c/0c/24841136476adafd26f94b45bb718a78cb0500bd7b4f8d667b67c29d7b0d/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05", size = 1856071 }, + { url = "https://files.pythonhosted.org/packages/53/5e/c32957a09cceb2af10d7642df45d1e3dbd8596061f700eac93b801de53c0/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4", size = 2038439 }, + { url = "https://files.pythonhosted.org/packages/e4/8f/979ab3eccd118b638cd6d8f980fea8794f45018255a36044dea40fe579d4/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f", size = 2787416 }, + { url = "https://files.pythonhosted.org/packages/02/1d/00f2e4626565b3b6d3690dab4d4fe1a26edd6a20e53749eb21ca892ef2df/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08", size = 2134548 }, + { url = "https://files.pythonhosted.org/packages/9d/46/3112621204128b90898adc2e721a3cd6cf5626504178d6f32c33b5a43b79/pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6", size = 1989882 }, + { url = "https://files.pythonhosted.org/packages/49/ec/557dd4ff5287ffffdf16a31d08d723de6762bb1b691879dc4423392309bc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807", size = 1995829 }, + { url = "https://files.pythonhosted.org/packages/6e/b2/610dbeb74d8d43921a7234555e4c091cb050a2bdb8cfea86d07791ce01c5/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c", size = 2091257 }, + { url = "https://files.pythonhosted.org/packages/8c/7f/4bf8e9d26a9118521c80b229291fa9558a07cdd9a968ec2d5c1026f14fbc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206", size = 2143894 }, + { url = "https://files.pythonhosted.org/packages/1f/1c/875ac7139c958f4390f23656fe696d1acc8edf45fb81e4831960f12cd6e4/pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c", size = 1816081 }, + { url = "https://files.pythonhosted.org/packages/d7/41/55a117acaeda25ceae51030b518032934f251b1dac3704a53781383e3491/pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17", size = 1981109 }, + { url = "https://files.pythonhosted.org/packages/27/39/46fe47f2ad4746b478ba89c561cafe4428e02b3573df882334bd2964f9cb/pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8", size = 1895553 }, + { url = "https://files.pythonhosted.org/packages/1c/00/0804e84a78b7fdb394fff4c4f429815a10e5e0993e6ae0e0b27dd20379ee/pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330", size = 1807220 }, + { url = "https://files.pythonhosted.org/packages/01/de/df51b3bac9820d38371f5a261020f505025df732ce566c2a2e7970b84c8c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52", size = 1829727 }, + { url = "https://files.pythonhosted.org/packages/5f/d9/c01d19da8f9e9fbdb2bf99f8358d145a312590374d0dc9dd8dbe484a9cde/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4", size = 1854282 }, + { url = "https://files.pythonhosted.org/packages/5f/84/7db66eb12a0dc88c006abd6f3cbbf4232d26adfd827a28638c540d8f871d/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c", size = 2037437 }, + { url = "https://files.pythonhosted.org/packages/34/ac/a2537958db8299fbabed81167d58cc1506049dba4163433524e06a7d9f4c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de", size = 2780899 }, + { url = "https://files.pythonhosted.org/packages/4a/c1/3e38cd777ef832c4fdce11d204592e135ddeedb6c6f525478a53d1c7d3e5/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025", size = 2135022 }, + { url = "https://files.pythonhosted.org/packages/7a/69/b9952829f80fd555fe04340539d90e000a146f2a003d3fcd1e7077c06c71/pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e", size = 1987969 }, + { url = "https://files.pythonhosted.org/packages/05/72/257b5824d7988af43460c4e22b63932ed651fe98804cc2793068de7ec554/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919", size = 1994625 }, + { url = "https://files.pythonhosted.org/packages/73/c3/78ed6b7f3278a36589bcdd01243189ade7fc9b26852844938b4d7693895b/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c", size = 2090089 }, + { url = "https://files.pythonhosted.org/packages/8d/c8/b4139b2f78579960353c4cd987e035108c93a78371bb19ba0dc1ac3b3220/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc", size = 2142496 }, + { url = "https://files.pythonhosted.org/packages/3e/f8/171a03e97eb36c0b51981efe0f78460554a1d8311773d3d30e20c005164e/pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9", size = 1811758 }, + { url = "https://files.pythonhosted.org/packages/6a/fe/4e0e63c418c1c76e33974a05266e5633e879d4061f9533b1706a86f77d5b/pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5", size = 1980864 }, + { url = "https://files.pythonhosted.org/packages/50/fc/93f7238a514c155a8ec02fc7ac6376177d449848115e4519b853820436c5/pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89", size = 1864327 }, + { url = "https://files.pythonhosted.org/packages/be/51/2e9b3788feb2aebff2aa9dfbf060ec739b38c05c46847601134cc1fed2ea/pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", size = 1895239 }, + { url = "https://files.pythonhosted.org/packages/7b/9e/f8063952e4a7d0127f5d1181addef9377505dcce3be224263b25c4f0bfd9/pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", size = 1805070 }, + { url = "https://files.pythonhosted.org/packages/2c/9d/e1d6c4561d262b52e41b17a7ef8301e2ba80b61e32e94520271029feb5d8/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", size = 1828096 }, + { url = "https://files.pythonhosted.org/packages/be/65/80ff46de4266560baa4332ae3181fffc4488ea7d37282da1a62d10ab89a4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", size = 1857708 }, + { url = "https://files.pythonhosted.org/packages/d5/ca/3370074ad758b04d9562b12ecdb088597f4d9d13893a48a583fb47682cdf/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", size = 2037751 }, + { url = "https://files.pythonhosted.org/packages/b1/e2/4ab72d93367194317b99d051947c071aef6e3eb95f7553eaa4208ecf9ba4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", size = 2733863 }, + { url = "https://files.pythonhosted.org/packages/8a/c6/8ae0831bf77f356bb73127ce5a95fe115b10f820ea480abbd72d3cc7ccf3/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", size = 2161161 }, + { url = "https://files.pythonhosted.org/packages/f1/f4/b2fe73241da2429400fc27ddeaa43e35562f96cf5b67499b2de52b528cad/pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", size = 1993294 }, + { url = "https://files.pythonhosted.org/packages/77/29/4bb008823a7f4cc05828198153f9753b3bd4c104d93b8e0b1bfe4e187540/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", size = 2001468 }, + { url = "https://files.pythonhosted.org/packages/f2/a9/0eaceeba41b9fad851a4107e0cf999a34ae8f0d0d1f829e2574f3d8897b0/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", size = 2091413 }, + { url = "https://files.pythonhosted.org/packages/d8/36/eb8697729725bc610fd73940f0d860d791dc2ad557faaefcbb3edbd2b349/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", size = 2154735 }, + { url = "https://files.pythonhosted.org/packages/52/e5/4f0fbd5c5995cc70d3afed1b5c754055bb67908f55b5cb8000f7112749bf/pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", size = 1833633 }, + { url = "https://files.pythonhosted.org/packages/ee/f2/c61486eee27cae5ac781305658779b4a6b45f9cc9d02c90cb21b940e82cc/pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", size = 1986973 }, + { url = "https://files.pythonhosted.org/packages/df/a6/e3f12ff25f250b02f7c51be89a294689d175ac76e1096c32bf278f29ca1e/pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", size = 1883215 }, + { url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033 }, + { url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542 }, + { url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854 }, + { url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389 }, + { url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934 }, + { url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176 }, + { url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720 }, + { url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972 }, + { url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477 }, + { url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186 }, + { url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429 }, + { url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713 }, + { url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897 }, + { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 }, + { url = "https://files.pythonhosted.org/packages/7c/60/e5eb2d462595ba1f622edbe7b1d19531e510c05c405f0b87c80c1e89d5b1/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6", size = 1894016 }, + { url = "https://files.pythonhosted.org/packages/61/20/da7059855225038c1c4326a840908cc7ca72c7198cb6addb8b92ec81c1d6/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676", size = 1771648 }, + { url = "https://files.pythonhosted.org/packages/8f/fc/5485cf0b0bb38da31d1d292160a4d123b5977841ddc1122c671a30b76cfd/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d", size = 1826929 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/fb1284a210e13a5f34c639efc54d51da136074ffbe25ec0c279cf9fbb1c4/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c", size = 1980591 }, + { url = "https://files.pythonhosted.org/packages/f1/14/77c1887a182d05af74f6aeac7b740da3a74155d3093ccc7ee10b900cc6b5/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27", size = 1981326 }, + { url = "https://files.pythonhosted.org/packages/06/aa/6f1b2747f811a9c66b5ef39d7f02fbb200479784c75e98290d70004b1253/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f", size = 1989205 }, + { url = "https://files.pythonhosted.org/packages/7a/d2/8ce2b074d6835f3c88d85f6d8a399790043e9fdb3d0e43455e72d19df8cc/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed", size = 2079616 }, + { url = "https://files.pythonhosted.org/packages/65/71/af01033d4e58484c3db1e5d13e751ba5e3d6b87cc3368533df4c50932c8b/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f", size = 2133265 }, + { url = "https://files.pythonhosted.org/packages/33/72/f881b5e18fbb67cf2fb4ab253660de3c6899dbb2dba409d0b757e3559e3d/pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c", size = 2001864 }, ] [[package]] @@ -828,81 +475,9 @@ dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/4e/9a5ab8745e7606b88c2c7ca223449ac9d82a71fd5e31df47b453f2cb39a1/pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220", size = 21940, upload-time = "2024-11-13T16:35:41.84Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/4e/9a5ab8745e7606b88c2c7ca223449ac9d82a71fd5e31df47b453f2cb39a1/pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220", size = 21940 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/26/c288cabf8cfc5a27e1aa9e5029b7682c0f920b8074f45d22bf844314d66a/pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60", size = 18581, upload-time = "2024-11-13T16:35:40.689Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, - { name = "pytest" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, -] - -[[package]] -name = "python-dotenv" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.32" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, -] - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, - { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, - { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, - { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/1b/26/c288cabf8cfc5a27e1aa9e5029b7682c0f920b8074f45d22bf844314d66a/pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60", size = 18581 }, ] [[package]] @@ -915,97 +490,83 @@ dependencies = [ { name = "lxml" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/ca/0c9e5afed873dd29f529f24bb3174d582f77e343acfa8c77a39745fa7073/readabilipy-0.2.0.tar.gz", hash = "sha256:098bf347b19f362042fb6c08864ad776588bf844ac2261fb230f7f9c250fdae5", size = 38948, upload-time = "2020-09-22T11:14:54.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/ca/0c9e5afed873dd29f529f24bb3174d582f77e343acfa8c77a39745fa7073/readabilipy-0.2.0.tar.gz", hash = "sha256:098bf347b19f362042fb6c08864ad776588bf844ac2261fb230f7f9c250fdae5", size = 38948 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/42/11f5795b747841912a6f7bacab32ab1eaabc911a4e9949fbf8786121f4d3/readabilipy-0.2.0-py3-none-any.whl", hash = "sha256:0050853cd6ab012ac75bb4d8f06427feb7dc32054da65060da44654d049802d0", size = 4339504, upload-time = "2020-09-22T11:14:51.007Z" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/39/42/11f5795b747841912a6f7bacab32ab1eaabc911a4e9949fbf8786121f4d3/readabilipy-0.2.0-py3-none-any.whl", hash = "sha256:0050853cd6ab012ac75bb4d8f06427feb7dc32054da65060da44654d049802d0", size = 4339504 }, ] [[package]] name = "regex" version = "2024.11.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674, upload-time = "2024-11-06T20:08:57.575Z" }, - { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684, upload-time = "2024-11-06T20:08:59.787Z" }, - { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589, upload-time = "2024-11-06T20:09:01.896Z" }, - { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511, upload-time = "2024-11-06T20:09:04.062Z" }, - { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149, upload-time = "2024-11-06T20:09:06.237Z" }, - { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707, upload-time = "2024-11-06T20:09:07.715Z" }, - { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702, upload-time = "2024-11-06T20:09:10.101Z" }, - { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976, upload-time = "2024-11-06T20:09:11.566Z" }, - { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397, upload-time = "2024-11-06T20:09:13.119Z" }, - { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726, upload-time = "2024-11-06T20:09:14.85Z" }, - { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098, upload-time = "2024-11-06T20:09:16.504Z" }, - { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325, upload-time = "2024-11-06T20:09:18.698Z" }, - { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277, upload-time = "2024-11-06T20:09:21.725Z" }, - { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197, upload-time = "2024-11-06T20:09:24.092Z" }, - { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714, upload-time = "2024-11-06T20:09:26.36Z" }, - { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042, upload-time = "2024-11-06T20:09:28.762Z" }, - { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669, upload-time = "2024-11-06T20:09:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684, upload-time = "2024-11-06T20:09:32.915Z" }, - { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589, upload-time = "2024-11-06T20:09:35.504Z" }, - { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121, upload-time = "2024-11-06T20:09:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275, upload-time = "2024-11-06T20:09:40.371Z" }, - { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257, upload-time = "2024-11-06T20:09:43.059Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727, upload-time = "2024-11-06T20:09:48.19Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667, upload-time = "2024-11-06T20:09:49.828Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963, upload-time = "2024-11-06T20:09:51.819Z" }, - { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700, upload-time = "2024-11-06T20:09:53.982Z" }, - { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592, upload-time = "2024-11-06T20:09:56.222Z" }, - { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929, upload-time = "2024-11-06T20:09:58.642Z" }, - { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213, upload-time = "2024-11-06T20:10:00.867Z" }, - { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734, upload-time = "2024-11-06T20:10:03.361Z" }, - { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052, upload-time = "2024-11-06T20:10:05.179Z" }, - { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781, upload-time = "2024-11-06T20:10:07.07Z" }, - { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455, upload-time = "2024-11-06T20:10:09.117Z" }, - { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759, upload-time = "2024-11-06T20:10:11.155Z" }, - { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976, upload-time = "2024-11-06T20:10:13.24Z" }, - { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077, upload-time = "2024-11-06T20:10:15.37Z" }, - { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160, upload-time = "2024-11-06T20:10:19.027Z" }, - { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896, upload-time = "2024-11-06T20:10:21.85Z" }, - { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997, upload-time = "2024-11-06T20:10:24.329Z" }, - { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725, upload-time = "2024-11-06T20:10:28.067Z" }, - { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481, upload-time = "2024-11-06T20:10:31.612Z" }, - { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896, upload-time = "2024-11-06T20:10:34.054Z" }, - { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138, upload-time = "2024-11-06T20:10:36.142Z" }, - { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692, upload-time = "2024-11-06T20:10:38.394Z" }, - { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135, upload-time = "2024-11-06T20:10:40.367Z" }, - { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567, upload-time = "2024-11-06T20:10:43.467Z" }, - { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload-time = "2024-11-06T20:10:45.19Z" }, - { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload-time = "2024-11-06T20:10:47.177Z" }, - { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload-time = "2024-11-06T20:10:49.312Z" }, - { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload-time = "2024-11-06T20:10:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload-time = "2024-11-06T20:10:52.926Z" }, - { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload-time = "2024-11-06T20:10:54.828Z" }, - { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload-time = "2024-11-06T20:10:56.634Z" }, - { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload-time = "2024-11-06T20:10:59.369Z" }, - { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload-time = "2024-11-06T20:11:02.042Z" }, - { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload-time = "2024-11-06T20:11:03.933Z" }, - { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload-time = "2024-11-06T20:11:06.497Z" }, - { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload-time = "2024-11-06T20:11:09.06Z" }, - { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload-time = "2024-11-06T20:11:11.256Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload-time = "2024-11-06T20:11:13.161Z" }, - { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload-time = "2024-11-06T20:11:15Z" }, + { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674 }, + { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511 }, + { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149 }, + { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707 }, + { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702 }, + { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976 }, + { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397 }, + { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726 }, + { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098 }, + { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325 }, + { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277 }, + { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197 }, + { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714 }, + { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042 }, + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, ] [[package]] name = "requests" -version = "2.33.1" +version = "2.32.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1013,183 +574,61 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, -] - -[[package]] -name = "rpds-py" -version = "0.30.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, - { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, - { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, - { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, - { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, - { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, - { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, - { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, - { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, - { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, - { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, - { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, - { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, - { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, - { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, - { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, - { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, - { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, - { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, - { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, - { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, - { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, - { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, - { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, - { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, - { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] [[package]] name = "ruff" version = "0.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222, upload-time = "2024-11-29T03:29:49.986Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605, upload-time = "2024-11-29T03:28:41.978Z" }, - { url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243, upload-time = "2024-11-29T03:28:44.577Z" }, - { url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739, upload-time = "2024-11-29T03:28:53.895Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153, upload-time = "2024-11-29T03:28:59.609Z" }, - { url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387, upload-time = "2024-11-29T03:29:02.512Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351, upload-time = "2024-11-29T03:29:04.838Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879, upload-time = "2024-11-29T03:29:07.202Z" }, - { url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354, upload-time = "2024-11-29T03:29:09.533Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976, upload-time = "2024-11-29T03:29:12.627Z" }, - { url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564, upload-time = "2024-11-29T03:29:16.594Z" }, - { url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604, upload-time = "2024-11-29T03:29:24.553Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071, upload-time = "2024-11-29T03:29:29.533Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657, upload-time = "2024-11-29T03:29:31.87Z" }, - { url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362, upload-time = "2024-11-29T03:29:34.255Z" }, - { url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476, upload-time = "2024-11-29T03:29:36.483Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463, upload-time = "2024-11-29T03:29:38.814Z" }, - { url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621, upload-time = "2024-11-29T03:29:43.977Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605 }, + { url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243 }, + { url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739 }, + { url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153 }, + { url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387 }, + { url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351 }, + { url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879 }, + { url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354 }, + { url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976 }, + { url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564 }, + { url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604 }, + { url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071 }, + { url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657 }, + { url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362 }, + { url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476 }, + { url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463 }, + { url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621 }, ] [[package]] name = "six" version = "1.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041, upload-time = "2021-05-05T14:18:18.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] [[package]] name = "soupsieve" -version = "2.8.4" +version = "2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, ] [[package]] @@ -1201,127 +640,60 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383, upload-time = "2024-08-01T08:52:48.659Z" }, + { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383 }, ] [[package]] name = "starlette" -version = "1.3.1" +version = "0.41.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, -] - -[[package]] -name = "tomli" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, + { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 }, ] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.12.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] [[package]] name = "urllib3" -version = "2.7.0" +version = "2.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, ] [[package]] name = "uvicorn" -version = "0.32.0" +version = "0.32.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/fc/1d785078eefd6945f3e5bab5c076e4230698046231eb0f3747bc5c8fa992/uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e", size = 77564, upload-time = "2024-10-15T17:27:33.848Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630 } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/14/78bd0e95dd2444b6caacbca2b730671d4295ccb628ef58b81bee903629df/uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82", size = 63723, upload-time = "2024-10-15T17:27:32.022Z" }, + { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 }, ] [[package]] name = "webencodings" version = "0.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, ] diff --git a/src/filesystem/Dockerfile b/src/filesystem/Dockerfile deleted file mode 100644 index 418b140..0000000 --- a/src/filesystem/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM node:22.12-alpine AS builder - -WORKDIR /app - -COPY src/filesystem /app -COPY tsconfig.json /tsconfig.json - -RUN --mount=type=cache,target=/root/.npm npm install - -RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev - - -FROM node:22-alpine AS release - -WORKDIR /app - -COPY --from=builder /app/dist /app/dist -COPY --from=builder /app/package.json /app/package.json -COPY --from=builder /app/package-lock.json /app/package-lock.json - -ENV NODE_ENV=production - -RUN npm ci --ignore-scripts --omit-dev - -ENTRYPOINT ["node", "/app/dist/index.js"] \ No newline at end of file diff --git a/src/filesystem/README.md b/src/filesystem/README.md index 5a7ffe7..c2950cd 100644 --- a/src/filesystem/README.md +++ b/src/filesystem/README.md @@ -2,8 +2,6 @@ Node.js server implementing Model Context Protocol (MCP) for filesystem operations. -Published on npm as [`@modelcontextprotocol/server-filesystem`](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem). - ## Features - Read/write files @@ -11,79 +9,21 @@ Published on npm as [`@modelcontextprotocol/server-filesystem`](https://www.npmj - Move files/directories - Search files - Get file metadata -- Dynamic directory access control via [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) - -## Directory Access Control - -The server uses a flexible directory access control system. Directories can be specified via command-line arguments or dynamically via [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots). - -### Method 1: Command-line Arguments -Specify Allowed directories when starting the server: -```bash -mcp-server-filesystem /path/to/dir1 /path/to/dir2 -``` - -### Method 2: MCP Roots (Recommended) -MCP clients that support [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) can dynamically update the Allowed directories. - -Roots notified by Client to Server, completely replace any server-side Allowed directories when provided. - -**Important**: If server starts without command-line arguments AND client doesn't support roots protocol (or provides empty roots), the server will throw an error during initialization. - -This is the recommended method, as this enables runtime directory updates via `roots/list_changed` notifications without server restart, providing a more flexible and modern integration experience. - -### How It Works - -The server's directory access control follows this flow: - -1. **Server Startup** - - Server starts with directories from command-line arguments (if provided) - - If no arguments provided, server starts with empty allowed directories - -2. **Client Connection & Initialization** - - Client connects and sends `initialize` request with capabilities - - Server checks if client supports roots protocol (`capabilities.roots`) - -3. **Roots Protocol Handling** (if client supports roots) - - **On initialization**: Server requests roots from client via `roots/list` - - Client responds with its configured roots - - Server replaces ALL allowed directories with client's roots - - **On runtime updates**: Client can send `notifications/roots/list_changed` - - Server requests updated roots and replaces allowed directories again - -4. **Fallback Behavior** (if client doesn't support roots) - - Server continues using command-line directories only - - No dynamic updates possible - -5. **Access Control** - - All filesystem operations are restricted to allowed directories - - Use `list_allowed_directories` tool to see current directories - - Server requires at least ONE allowed directory to operate - -**Note**: The server will only allow operations within directories specified either via `args` or via Roots. - +**Note**: The server will only allow operations within directories specified via `args`. ## API +### Resources + +- `file://system`: File system operations interface + ### Tools -- **read_text_file** - - Read complete contents of a file as text - - Inputs: - - `path` (string) - - `head` (number, optional): First N lines - - `tail` (number, optional): Last N lines - - Always treats the file as UTF-8 text regardless of extension - - Cannot specify both `head` and `tail` simultaneously - -- **read_media_file** - - Read a file and return it as a base64-encoded content block with its MIME type - - Inputs: - - `path` (string) - - Streams the file and returns base64 data with the corresponding MIME type. Image and - audio files are returned as `image`/`audio` content; any other file type is returned as - an embedded `resource` (a valid MCP content block for arbitrary binary data) +- **read_file** + - Read complete contents of a file + - Input: `path` (string) + - Reads complete file contents with UTF-8 encoding - **read_multiple_files** - Read multiple files simultaneously @@ -96,24 +36,6 @@ The server's directory access control follows this flow: - `path` (string): File location - `content` (string): File content -- **edit_file** - - Make selective edits using advanced pattern matching and formatting - - Features: - - Line-based and multi-line content matching - - Whitespace normalization with indentation preservation - - Multiple simultaneous edits with correct positioning - - Indentation style detection and preservation - - Git-style diff output with context - - Preview changes with dry run mode - - Inputs: - - `path` (string): File to edit - - `edits` (array): List of edit operations - - `oldText` (string): Text to search for (can be substring) - - `newText` (string): Text to replace with - - `dryRun` (boolean): Preview changes without applying (default: false) - - Returns detailed diff and match information for dry runs, otherwise applies changes - - Best Practice: Always use dryRun first to preview changes before applying them - - **create_directory** - Create new directory or ensure it exists - Input: `path` (string) @@ -124,14 +46,6 @@ The server's directory access control follows this flow: - List directory contents with [FILE] or [DIR] prefixes - Input: `path` (string) -- **list_directory_with_sizes** - - List directory contents with [FILE] or [DIR] prefixes, including file sizes - - Inputs: - - `path` (string): Directory path to list - - `sortBy` (string, optional): Sort entries by "name" or "size" (default: "name") - - Returns detailed listing with file sizes and summary statistics - - Shows total files, directories, and combined size - - **move_file** - Move or rename files and directories - Inputs: @@ -140,28 +54,13 @@ The server's directory access control follows this flow: - Fails if destination exists - **search_files** - - Recursively search for files/directories that match or do not match patterns + - Recursively search for files/directories - Inputs: - `path` (string): Starting directory - `pattern` (string): Search pattern - - `excludePatterns` (string[]): Exclude any patterns. - - Glob-style pattern matching + - Case-insensitive matching - Returns full paths to matches -- **directory_tree** - - Get recursive JSON tree structure of directory contents - - Inputs: - - `path` (string): Starting directory - - `excludePatterns` (string[]): Exclude any patterns. Glob formats are supported. - - Returns: - - JSON array where each entry contains: - - `name` (string): File/directory name - - `type` ('file'|'directory'): Entry type - - `children` (array): Present only for directories - - Empty array for empty directories - - Omitted for files - - Output is formatted with 2-space indentation for readability - - **get_file_info** - Get detailed file/directory metadata - Input: `path` (string) @@ -179,66 +78,8 @@ The server's directory access control follows this flow: - Returns: - Directories that this server can read/write from -### Tool annotations (MCP hints) - -This server sets [MCP ToolAnnotations](https://modelcontextprotocol.io/specification/2025-03-26/server/tools#toolannotations) -on each tool so clients can: - -- Distinguish **readโ€‘only** tools from writeโ€‘capable tools. -- Understand which write operations are **idempotent** (safe to retry with the same arguments). -- Highlight operations that may be **destructive** (overwriting or heavily mutating data). -- Signal that a tool does **not** reach an open or external world (every filesystem tool sets `openWorldHint: false`). - -The mapping for filesystem tools is: - -| Tool | readOnlyHint | idempotentHint | destructiveHint | Notes | -|-----------------------------|--------------|----------------|-----------------|--------------------------------------------------| -| `read_text_file` | `true` | โ€“ | โ€“ | Pure read | -| `read_media_file` | `true` | โ€“ | โ€“ | Pure read | -| `read_multiple_files` | `true` | โ€“ | โ€“ | Pure read | -| `list_directory` | `true` | โ€“ | โ€“ | Pure read | -| `list_directory_with_sizes` | `true` | โ€“ | โ€“ | Pure read | -| `directory_tree` | `true` | โ€“ | โ€“ | Pure read | -| `search_files` | `true` | โ€“ | โ€“ | Pure read | -| `get_file_info` | `true` | โ€“ | โ€“ | Pure read | -| `list_allowed_directories` | `true` | โ€“ | โ€“ | Pure read | -| `create_directory` | `false` | `true` | `false` | Reโ€‘creating the same dir is a noโ€‘op | -| `write_file` | `false` | `true` | `true` | Overwrites existing files | -| `edit_file` | `false` | `false` | `true` | Reโ€‘applying edits can fail or doubleโ€‘apply | -| `move_file` | `false` | `false` | `true` | Deletes source file | - -> Note: `idempotentHint` and `destructiveHint` are meaningful only when `readOnlyHint` is `false`, as defined by the MCP spec. Every tool also sets `openWorldHint: false` โ€” this server only accesses the local filesystem within its allowed directories, never an open or external world. - ## Usage with Claude Desktop Add this to your `claude_desktop_config.json`: - -Note: you can provide sandboxed directories to the server by mounting them to `/projects`. Adding the `ro` flag will make the directory readonly by the server. - -### Docker -Note: all directories must be mounted to `/projects` by default. - -```json -{ - "mcpServers": { - "filesystem": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "--mount", "type=bind,src=/Users/username/Desktop,dst=/projects/Desktop", - "--mount", "type=bind,src=/path/to/other/allowed/dir,dst=/projects/other/allowed/dir,ro", - "--mount", "type=bind,src=/path/to/file.txt,dst=/projects/path/to/file.txt", - "mcp/filesystem", - "/projects" - ] - } - } -} -``` - -### NPX - ```json { "mcpServers": { @@ -255,111 +96,6 @@ Note: all directories must be mounted to `/projects` by default. } ``` -On Windows, use `cmd /c` to launch `npx`: - -```json -{ - "mcpServers": { - "filesystem": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-filesystem", - "/Users/username/Desktop", - "/path/to/other/allowed/dir" - ] - } - } -} -``` - -## Usage with VS Code - -For quick installation, click the installation buttons below... - -[![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=filesystem&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-filesystem%22%2C%22%24%7BworkspaceFolder%7D%22%5D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=filesystem&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-filesystem%22%2C%22%24%7BworkspaceFolder%7D%22%5D%7D&quality=insiders) - -[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=filesystem&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22--mount%22%2C%22type%3Dbind%2Csrc%3D%24%7BworkspaceFolder%7D%2Cdst%3D%2Fprojects%2Fworkspace%22%2C%22mcp%2Ffilesystem%22%2C%22%2Fprojects%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=filesystem&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22--mount%22%2C%22type%3Dbind%2Csrc%3D%24%7BworkspaceFolder%7D%2Cdst%3D%2Fprojects%2Fworkspace%22%2C%22mcp%2Ffilesystem%22%2C%22%2Fprojects%22%5D%7D&quality=insiders) - -For manual installation, you can configure the MCP server using one of these methods: - -**Method 1: User Configuration (Recommended)** -Add the configuration to your user-level MCP configuration file. Open the Command Palette (`Ctrl + Shift + P`) and run `MCP: Open User Configuration`. This will open your user `mcp.json` file where you can add the server configuration. - -**Method 2: Workspace Configuration** -Alternatively, you can add the configuration to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. - -> For more details about MCP configuration in VS Code, see the [official VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers). - -You can provide sandboxed directories to the server by mounting them to `/projects`. Adding the `ro` flag will make the directory readonly by the server. - -### Docker -Note: all directories must be mounted to `/projects` by default. - -```json -{ - "servers": { - "filesystem": { - "command": "docker", - "args": [ - "run", - "-i", - "--rm", - "--mount", "type=bind,src=${workspaceFolder},dst=/projects/workspace", - "mcp/filesystem", - "/projects" - ] - } - } -} -``` - -### NPX - -```json -{ - "servers": { - "filesystem": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-filesystem", - "${workspaceFolder}" - ] - } - } -} -``` - -On Windows, use: - -```json -{ - "servers": { - "filesystem": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-filesystem", - "${workspaceFolder}" - ] - } - } -} -``` - -## Build - -Docker build: - -```bash -docker build -t mcp/filesystem -f src/filesystem/Dockerfile . -``` - ## License This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/filesystem/__tests__/directory-tree.mcp-sdk.test.ts b/src/filesystem/__tests__/directory-tree.mcp-sdk.test.ts deleted file mode 100644 index 0cbe48c..0000000 --- a/src/filesystem/__tests__/directory-tree.mcp-sdk.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import * as os from 'os'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; - -describe('directory_tree MCP SDK regression', () => { - let client: Client; - let transport: StdioClientTransport; - let testDir: string; - - beforeEach(async () => { - const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-fs-tree-')); - testDir = await fs.realpath(tmp); - - await fs.writeFile(path.join(testDir, 'root.txt'), 'root'); - await fs.mkdir(path.join(testDir, 'nested')); - await fs.writeFile(path.join(testDir, 'nested', 'child.txt'), 'child'); - - const serverPath = path.resolve(__dirname, '../dist/index.js'); - transport = new StdioClientTransport({ - command: 'node', - args: [serverPath, testDir], - }); - - client = new Client( - { name: 'directory-tree-regression-test', version: '1.0.0' }, - { capabilities: {} }, - ); - - await client.connect(transport); - }); - - afterEach(async () => { - await client?.close(); - await fs.rm(testDir, { recursive: true, force: true }); - }); - - it('returns structuredContent.content as a string (not an array) when called via MCP SDK', async () => { - const result = await client.callTool({ - name: 'directory_tree', - arguments: { path: testDir }, - }); - - // Regression test for issues where structuredContent was returned as an array, - // which causes MCP SDK validation to throw -32602 (invalid structured content). - const structured = result.structuredContent as { content: unknown }; - expect(structured).toBeDefined(); - expect(typeof structured.content).toBe('string'); - expect(Array.isArray(structured.content)).toBe(false); - - const parsed = JSON.parse(structured.content as string); - expect(Array.isArray(parsed)).toBe(true); - expect(parsed.length).toBeGreaterThan(0); - }); -}); diff --git a/src/filesystem/__tests__/directory-tree.test.ts b/src/filesystem/__tests__/directory-tree.test.ts deleted file mode 100644 index 04c8278..0000000 --- a/src/filesystem/__tests__/directory-tree.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import * as os from 'os'; - -// We need to test the buildTree function, but it's defined inside the request handler -// So we'll extract the core logic into a testable function -import { minimatch } from 'minimatch'; - -interface TreeEntry { - name: string; - type: 'file' | 'directory'; - children?: TreeEntry[]; -} - -async function buildTreeForTesting(currentPath: string, rootPath: string, excludePatterns: string[] = []): Promise { - const entries = await fs.readdir(currentPath, {withFileTypes: true}); - const result: TreeEntry[] = []; - - for (const entry of entries) { - const relativePath = path.relative(rootPath, path.join(currentPath, entry.name)); - const shouldExclude = excludePatterns.some(pattern => { - if (pattern.includes('*')) { - return minimatch(relativePath, pattern, {dot: true}); - } - // For files: match exact name or as part of path - // For directories: match as directory path - return minimatch(relativePath, pattern, {dot: true}) || - minimatch(relativePath, `**/${pattern}`, {dot: true}) || - minimatch(relativePath, `**/${pattern}/**`, {dot: true}); - }); - if (shouldExclude) - continue; - - const entryData: TreeEntry = { - name: entry.name, - type: entry.isDirectory() ? 'directory' : 'file' - }; - - if (entry.isDirectory()) { - const subPath = path.join(currentPath, entry.name); - entryData.children = await buildTreeForTesting(subPath, rootPath, excludePatterns); - } - - result.push(entryData); - } - - return result; -} - -describe('buildTree exclude patterns', () => { - let testDir: string; - - beforeEach(async () => { - testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'filesystem-test-')); - - // Create test directory structure - await fs.mkdir(path.join(testDir, 'src')); - await fs.mkdir(path.join(testDir, 'node_modules')); - await fs.mkdir(path.join(testDir, '.git')); - await fs.mkdir(path.join(testDir, 'nested', 'node_modules'), { recursive: true }); - - // Create test files - await fs.writeFile(path.join(testDir, '.env'), 'SECRET=value'); - await fs.writeFile(path.join(testDir, '.env.local'), 'LOCAL_SECRET=value'); - await fs.writeFile(path.join(testDir, 'src', 'index.js'), 'console.log("hello");'); - await fs.writeFile(path.join(testDir, 'package.json'), '{}'); - await fs.writeFile(path.join(testDir, 'node_modules', 'module.js'), 'module.exports = {};'); - await fs.writeFile(path.join(testDir, 'nested', 'node_modules', 'deep.js'), 'module.exports = {};'); - }); - - afterEach(async () => { - await fs.rm(testDir, { recursive: true, force: true }); - }); - - it('should exclude files matching simple patterns', async () => { - // Test the current implementation - this will fail until the bug is fixed - const tree = await buildTreeForTesting(testDir, testDir, ['.env']); - const fileNames = tree.map(entry => entry.name); - - expect(fileNames).not.toContain('.env'); - expect(fileNames).toContain('.env.local'); // Should not exclude this - expect(fileNames).toContain('src'); - expect(fileNames).toContain('package.json'); - }); - - it('should exclude directories matching simple patterns', async () => { - const tree = await buildTreeForTesting(testDir, testDir, ['node_modules']); - const dirNames = tree.map(entry => entry.name); - - expect(dirNames).not.toContain('node_modules'); - expect(dirNames).toContain('src'); - expect(dirNames).toContain('.git'); - }); - - it('should exclude nested directories with same pattern', async () => { - const tree = await buildTreeForTesting(testDir, testDir, ['node_modules']); - - // Find the nested directory - const nestedDir = tree.find(entry => entry.name === 'nested'); - expect(nestedDir).toBeDefined(); - expect(nestedDir!.children).toBeDefined(); - - // The nested/node_modules should also be excluded - const nestedChildren = nestedDir!.children!.map(child => child.name); - expect(nestedChildren).not.toContain('node_modules'); - }); - - it('should handle glob patterns correctly', async () => { - const tree = await buildTreeForTesting(testDir, testDir, ['*.env']); - const fileNames = tree.map(entry => entry.name); - - expect(fileNames).not.toContain('.env'); - expect(fileNames).toContain('.env.local'); // *.env should not match .env.local - expect(fileNames).toContain('src'); - }); - - it('should handle dot files correctly', async () => { - const tree = await buildTreeForTesting(testDir, testDir, ['.git']); - const dirNames = tree.map(entry => entry.name); - - expect(dirNames).not.toContain('.git'); - expect(dirNames).toContain('.env'); // Should not exclude this - }); - - it('should work with multiple exclude patterns', async () => { - const tree = await buildTreeForTesting(testDir, testDir, ['node_modules', '.env', '.git']); - const entryNames = tree.map(entry => entry.name); - - expect(entryNames).not.toContain('node_modules'); - expect(entryNames).not.toContain('.env'); - expect(entryNames).not.toContain('.git'); - expect(entryNames).toContain('src'); - expect(entryNames).toContain('package.json'); - }); - - it('should handle empty exclude patterns', async () => { - const tree = await buildTreeForTesting(testDir, testDir, []); - const entryNames = tree.map(entry => entry.name); - - // All entries should be included - expect(entryNames).toContain('node_modules'); - expect(entryNames).toContain('.env'); - expect(entryNames).toContain('.git'); - expect(entryNames).toContain('src'); - }); -}); \ No newline at end of file diff --git a/src/filesystem/__tests__/lib.test.ts b/src/filesystem/__tests__/lib.test.ts deleted file mode 100644 index 24eb18a..0000000 --- a/src/filesystem/__tests__/lib.test.ts +++ /dev/null @@ -1,890 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import fs from 'fs/promises'; -import path from 'path'; -import os from 'os'; -import { - // Pure utility functions - formatSize, - normalizeLineEndings, - createUnifiedDiff, - // Security & validation functions - validatePath, - setAllowedDirectories, - // File operations - getFileStats, - readFileContent, - writeFileContent, - moveFile, - // Search & filtering functions - searchFilesWithValidation, - // File editing functions - applyFileEdits, - tailFile, - headFile -} from '../lib.js'; - -// Mock fs module -vi.mock('fs/promises'); -const mockFs = fs as any; - -function createMockFileHandle(content: Buffer) { - return { - read: vi.fn( - async (buffer: Buffer, offset: number, length: number, position: number) => { - const bytesRead = content.copy( - buffer, - offset, - position, - Math.min(position + length, content.length), - ); - return { bytesRead, buffer }; - }, - ), - close: vi.fn().mockResolvedValue(undefined), - }; -} - -describe('Lib Functions', () => { - beforeEach(() => { - vi.clearAllMocks(); - // Set up allowed directories for tests - const allowedDirs = process.platform === 'win32' ? ['C:\\Users\\test', 'C:\\temp', 'C:\\allowed'] : ['/home/user', '/tmp', '/allowed']; - setAllowedDirectories(allowedDirs); - }); - - afterEach(() => { - vi.restoreAllMocks(); - // Clear allowed directories after tests - setAllowedDirectories([]); - }); - - describe('Pure Utility Functions', () => { - describe('formatSize', () => { - it('formats bytes correctly', () => { - expect(formatSize(0)).toBe('0 B'); - expect(formatSize(512)).toBe('512 B'); - expect(formatSize(1024)).toBe('1.00 KB'); - expect(formatSize(1536)).toBe('1.50 KB'); - expect(formatSize(1048576)).toBe('1.00 MB'); - expect(formatSize(1073741824)).toBe('1.00 GB'); - expect(formatSize(1099511627776)).toBe('1.00 TB'); - }); - - it('handles edge cases', () => { - expect(formatSize(1023)).toBe('1023 B'); - expect(formatSize(1025)).toBe('1.00 KB'); - expect(formatSize(1048575)).toBe('1024.00 KB'); - }); - - it('handles very large numbers beyond TB', () => { - // The function only supports up to TB, so very large numbers will show as TB - expect(formatSize(1024 * 1024 * 1024 * 1024 * 1024)).toBe('1024.00 TB'); - expect(formatSize(Number.MAX_SAFE_INTEGER)).toContain('TB'); - }); - - it('handles negative numbers', () => { - // Negative numbers should return '0 B' as file sizes cannot be negative - expect(formatSize(-1)).toBe('0 B'); - expect(formatSize(-1024)).toBe('0 B'); - expect(formatSize(-1000000)).toBe('0 B'); - expect(formatSize(-0)).toBe('0 B'); - }); - - it('handles decimal numbers', () => { - expect(formatSize(1536.5)).toBe('1.50 KB'); - expect(formatSize(1023.9)).toBe('1023.9 B'); - }); - - it('handles very small positive numbers', () => { - expect(formatSize(1)).toBe('1 B'); - expect(formatSize(0.5)).toBe('0.5 B'); - expect(formatSize(0.1)).toBe('0.1 B'); - }); - }); - - describe('normalizeLineEndings', () => { - it('converts CRLF to LF', () => { - expect(normalizeLineEndings('line1\r\nline2\r\nline3')).toBe('line1\nline2\nline3'); - }); - - it('leaves LF unchanged', () => { - expect(normalizeLineEndings('line1\nline2\nline3')).toBe('line1\nline2\nline3'); - }); - - it('handles mixed line endings', () => { - expect(normalizeLineEndings('line1\r\nline2\nline3\r\n')).toBe('line1\nline2\nline3\n'); - }); - - it('handles empty string', () => { - expect(normalizeLineEndings('')).toBe(''); - }); - }); - - describe('createUnifiedDiff', () => { - it('creates diff for simple changes', () => { - const original = 'line1\nline2\nline3'; - const modified = 'line1\nmodified line2\nline3'; - const diff = createUnifiedDiff(original, modified, 'test.txt'); - - expect(diff).toContain('--- test.txt'); - expect(diff).toContain('+++ test.txt'); - expect(diff).toContain('-line2'); - expect(diff).toContain('+modified line2'); - }); - - it('handles CRLF normalization', () => { - const original = 'line1\r\nline2\r\n'; - const modified = 'line1\nmodified line2\n'; - const diff = createUnifiedDiff(original, modified); - - expect(diff).toContain('-line2'); - expect(diff).toContain('+modified line2'); - }); - - it('handles identical content', () => { - const content = 'line1\nline2\nline3'; - const diff = createUnifiedDiff(content, content); - - // Should not contain any +/- lines for identical content (excluding header lines) - expect(diff.split('\n').filter((line: string) => line.startsWith('+++') || line.startsWith('---'))).toHaveLength(2); - expect(diff.split('\n').filter((line: string) => line.startsWith('+') && !line.startsWith('+++'))).toHaveLength(0); - expect(diff.split('\n').filter((line: string) => line.startsWith('-') && !line.startsWith('---'))).toHaveLength(0); - }); - - it('handles empty content', () => { - const diff = createUnifiedDiff('', ''); - expect(diff).toContain('--- file'); - expect(diff).toContain('+++ file'); - }); - - it('handles default filename parameter', () => { - const diff = createUnifiedDiff('old', 'new'); - expect(diff).toContain('--- file'); - expect(diff).toContain('+++ file'); - }); - - it('handles custom filename', () => { - const diff = createUnifiedDiff('old', 'new', 'custom.txt'); - expect(diff).toContain('--- custom.txt'); - expect(diff).toContain('+++ custom.txt'); - }); - }); - }); - - describe('Security & Validation Functions', () => { - describe('validatePath', () => { - it('rejects Windows drive paths on POSIX hosts', async () => { - if (process.platform === 'win32') return; - - await expect(validatePath('C:\\Users\\me\\notes\\file.md')) - .rejects.toThrow('Windows-style path received on a POSIX host'); - }); - - // Use Windows-compatible paths for testing - const allowedDirs = process.platform === 'win32' ? ['C:\\Users\\test', 'C:\\temp'] : ['/home/user', '/tmp']; - - beforeEach(() => { - mockFs.realpath.mockImplementation(async (path: any) => path.toString()); - }); - - it('validates allowed paths', async () => { - const testPath = process.platform === 'win32' ? 'C:\\Users\\test\\file.txt' : '/home/user/file.txt'; - const result = await validatePath(testPath); - expect(result).toBe(testPath); - }); - - it('rejects disallowed paths', async () => { - const testPath = process.platform === 'win32' ? 'C:\\Windows\\System32\\file.txt' : '/etc/passwd'; - await expect(validatePath(testPath)) - .rejects.toThrow('Access denied - path outside allowed directories'); - }); - - it('handles non-existent files by checking parent directory', async () => { - const newFilePath = process.platform === 'win32' ? 'C:\\Users\\test\\newfile.txt' : '/home/user/newfile.txt'; - const parentPath = process.platform === 'win32' ? 'C:\\Users\\test' : '/home/user'; - - // Create an error with the ENOENT code that the implementation checks for - const enoentError = new Error('ENOENT') as NodeJS.ErrnoException; - enoentError.code = 'ENOENT'; - - mockFs.realpath - .mockRejectedValueOnce(enoentError) - .mockResolvedValueOnce(parentPath); - - const result = await validatePath(newFilePath); - expect(result).toBe(path.resolve(newFilePath)); - }); - - it('walks past multiple missing ancestors to an existing allowed directory', async () => { - // e.g. create_directory('/home/user/nonexistent/nested/newfile.txt') when - // neither 'nonexistent' nor 'nonexistent/nested' exist yet, but '/home/user' - // (an allowed directory) does. Regression test for #4629. - const newFilePath = process.platform === 'win32' ? 'C:\\Users\\test\\nonexistent\\nested\\newfile.txt' : '/home/user/nonexistent/nested/newfile.txt'; - - const enoentError = new Error('ENOENT') as NodeJS.ErrnoException; - enoentError.code = 'ENOENT'; - - // The path itself is missing. The resolver then realpaths the allowed - // directory ('/home/user'), which falls through to the beforeEach base - // implementation and echoes the path back as-is. readdir is mocked to - // undefined, so every component below it counts as missing. - mockFs.realpath.mockRejectedValueOnce(enoentError); - - const result = await validatePath(newFilePath); - expect(result).toBe(path.resolve(newFilePath)); - }); - - it('rejects when no ancestor directory exists at all', async () => { - const newFilePath = process.platform === 'win32' ? 'C:\\Users\\test\\nonexistent\\newfile.txt' : '/home/user/nonexistent/newfile.txt'; - - const enoentError = new Error('ENOENT') as NodeJS.ErrnoException; - enoentError.code = 'ENOENT'; - - // Every ancestor, all the way up to the filesystem root, is missing. - mockFs.realpath.mockRejectedValue(enoentError); - - await expect(validatePath(newFilePath)) - .rejects.toThrow('Parent directory does not exist'); - }); - - it('resolves relative paths against allowed directories instead of process.cwd()', async () => { - const relativePath = 'test-file.txt'; - const originalCwd = process.cwd; - - // Mock process.cwd to return a directory outside allowed directories - const disallowedCwd = process.platform === 'win32' ? 'C:\\Windows\\System32' : '/root'; - (process as any).cwd = vi.fn(() => disallowedCwd); - - try { - const result = await validatePath(relativePath); - - // Result should be resolved against first allowed directory, not process.cwd() - const expectedPath = process.platform === 'win32' - ? path.resolve('C:\\Users\\test', relativePath) - : path.resolve('/home/user', relativePath); - - expect(result).toBe(expectedPath); - expect(result).not.toContain(disallowedCwd); - } finally { - // Restore original process.cwd - process.cwd = originalCwd; - } - }); - }); - }); - - describe('File Operations', () => { - describe('getFileStats', () => { - it('returns file statistics', async () => { - const mockStats = { - size: 1024, - birthtime: new Date('2023-01-01'), - mtime: new Date('2023-01-02'), - atime: new Date('2023-01-03'), - isDirectory: () => false, - isFile: () => true, - mode: 0o644 - }; - - mockFs.stat.mockResolvedValueOnce(mockStats as any); - - const result = await getFileStats('/test/file.txt'); - - expect(result).toEqual({ - size: 1024, - created: new Date('2023-01-01'), - modified: new Date('2023-01-02'), - accessed: new Date('2023-01-03'), - isDirectory: false, - isFile: true, - permissions: '644' - }); - }); - - it('handles directory statistics', async () => { - const mockStats = { - size: 4096, - birthtime: new Date('2023-01-01'), - mtime: new Date('2023-01-02'), - atime: new Date('2023-01-03'), - isDirectory: () => true, - isFile: () => false, - mode: 0o755 - }; - - mockFs.stat.mockResolvedValueOnce(mockStats as any); - - const result = await getFileStats('/test/dir'); - - expect(result.isDirectory).toBe(true); - expect(result.isFile).toBe(false); - expect(result.permissions).toBe('755'); - }); - }); - - describe('readFileContent', () => { - it('reads file with default encoding', async () => { - mockFs.readFile.mockResolvedValueOnce('file content'); - - const result = await readFileContent('/test/file.txt'); - - expect(result).toBe('file content'); - expect(mockFs.readFile).toHaveBeenCalledWith('/test/file.txt', 'utf-8'); - }); - - it('reads file with custom encoding', async () => { - mockFs.readFile.mockResolvedValueOnce('file content'); - - const result = await readFileContent('/test/file.txt', 'ascii'); - - expect(result).toBe('file content'); - expect(mockFs.readFile).toHaveBeenCalledWith('/test/file.txt', 'ascii'); - }); - }); - - describe('writeFileContent', () => { - it('writes file content', async () => { - mockFs.writeFile.mockResolvedValueOnce(undefined); - - await writeFileContent('/test/file.txt', 'new content'); - - expect(mockFs.writeFile).toHaveBeenCalledWith('/test/file.txt', 'new content', { encoding: "utf-8", flag: 'wx' }); - }); - - it('preserves file permissions when overwriting existing file', async () => { - // First writeFile call with 'wx' flag fails because file exists - mockFs.writeFile.mockRejectedValueOnce(Object.assign(new Error('EEXIST'), { code: 'EEXIST' })); - // stat returns executable permissions - mockFs.stat.mockResolvedValueOnce({ mode: 0o100755 }); - // Second writeFile (to temp) succeeds - mockFs.writeFile.mockResolvedValueOnce(undefined); - mockFs.rename.mockResolvedValueOnce(undefined); - mockFs.chmod.mockResolvedValueOnce(undefined); - - await writeFileContent('/test/script.sh', 'new content'); - - expect(mockFs.stat).toHaveBeenCalledWith('/test/script.sh'); - expect(mockFs.chmod).toHaveBeenCalledWith('/test/script.sh', 0o755); - }); - - it('does not fail the write when chmod fails', async () => { - mockFs.writeFile.mockRejectedValueOnce(Object.assign(new Error('EEXIST'), { code: 'EEXIST' })); - mockFs.stat.mockResolvedValueOnce({ mode: 0o100755 }); - mockFs.writeFile.mockResolvedValueOnce(undefined); - mockFs.rename.mockResolvedValueOnce(undefined); - mockFs.chmod.mockRejectedValueOnce(Object.assign(new Error('EPERM'), { code: 'EPERM' })); - - await expect(writeFileContent('/test/script.sh', 'new content')).resolves.toBeUndefined(); - expect(mockFs.unlink).not.toHaveBeenCalled(); - }); - }); - - describe('moveFile', () => { - it('moves the file when the destination does not exist', async () => { - const enoent = Object.assign(new Error('not found'), { code: 'ENOENT' }); - mockFs.lstat.mockRejectedValueOnce(enoent); - mockFs.rename.mockResolvedValueOnce(undefined); - - await moveFile('/test/source.txt', '/test/dest.txt'); - - expect(mockFs.rename).toHaveBeenCalledWith('/test/source.txt', '/test/dest.txt'); - }); - - it('fails without overwriting when the destination already exists', async () => { - // lstat resolving means the destination is occupied. - mockFs.lstat.mockResolvedValueOnce({} as any); - - await expect(moveFile('/test/source.txt', '/test/dest.txt')).rejects.toThrow( - 'Destination already exists' - ); - - expect(mockFs.rename).not.toHaveBeenCalled(); - }); - }); - - }); - - describe('Search & Filtering Functions', () => { - describe('searchFilesWithValidation', () => { - beforeEach(() => { - mockFs.realpath.mockImplementation(async (path: any) => path.toString()); - }); - - - it('excludes files matching exclude patterns', async () => { - const mockEntries = [ - { name: 'test.txt', isDirectory: () => false }, - { name: 'test.log', isDirectory: () => false }, - { name: 'node_modules', isDirectory: () => true } - ]; - - mockFs.readdir.mockResolvedValueOnce(mockEntries as any); - - const testDir = process.platform === 'win32' ? 'C:\\allowed\\dir' : '/allowed/dir'; - const allowedDirs = process.platform === 'win32' ? ['C:\\allowed'] : ['/allowed']; - - // Mock realpath to return the same path for validation to pass - mockFs.realpath.mockImplementation(async (inputPath: any) => { - const pathStr = inputPath.toString(); - // Return the path as-is for validation - return pathStr; - }); - - const result = await searchFilesWithValidation( - testDir, - '*test*', - allowedDirs, - { excludePatterns: ['*.log', 'node_modules'] } - ); - - const expectedResult = process.platform === 'win32' ? 'C:\\allowed\\dir\\test.txt' : '/allowed/dir/test.txt'; - expect(result).toEqual([expectedResult]); - }); - - it('handles validation errors during search', async () => { - const mockEntries = [ - { name: 'test.txt', isDirectory: () => false }, - { name: 'invalid_file.txt', isDirectory: () => false } - ]; - - mockFs.readdir.mockResolvedValueOnce(mockEntries as any); - - // Mock validatePath to throw error for invalid_file.txt - mockFs.realpath.mockImplementation(async (path: any) => { - if (path.toString().includes('invalid_file.txt')) { - throw new Error('Access denied'); - } - return path.toString(); - }); - - const testDir = process.platform === 'win32' ? 'C:\\allowed\\dir' : '/allowed/dir'; - const allowedDirs = process.platform === 'win32' ? ['C:\\allowed'] : ['/allowed']; - - const result = await searchFilesWithValidation( - testDir, - '*test*', - allowedDirs, - {} - ); - - // Should only return the valid file, skipping the invalid one - const expectedResult = process.platform === 'win32' ? 'C:\\allowed\\dir\\test.txt' : '/allowed/dir/test.txt'; - expect(result).toEqual([expectedResult]); - }); - - it('handles complex exclude patterns with wildcards', async () => { - const mockEntries = [ - { name: 'test.txt', isDirectory: () => false }, - { name: 'test.backup', isDirectory: () => false }, - { name: 'important_test.js', isDirectory: () => false } - ]; - - mockFs.readdir.mockResolvedValueOnce(mockEntries as any); - - const testDir = process.platform === 'win32' ? 'C:\\allowed\\dir' : '/allowed/dir'; - const allowedDirs = process.platform === 'win32' ? ['C:\\allowed'] : ['/allowed']; - - const result = await searchFilesWithValidation( - testDir, - '*test*', - allowedDirs, - { excludePatterns: ['*.backup'] } - ); - - const expectedResults = process.platform === 'win32' ? [ - 'C:\\allowed\\dir\\test.txt', - 'C:\\allowed\\dir\\important_test.js' - ] : [ - '/allowed/dir/test.txt', - '/allowed/dir/important_test.js' - ]; - expect(result).toEqual(expectedResults); - }); - }); - }); - - describe('File Editing Functions', () => { - describe('applyFileEdits', () => { - beforeEach(() => { - mockFs.readFile.mockResolvedValue('line1\nline2\nline3\n'); - mockFs.writeFile.mockResolvedValue(undefined); - mockFs.stat.mockResolvedValue({ mode: 0o100644 }); - mockFs.chmod.mockResolvedValue(undefined); - }); - - it('applies simple text replacement', async () => { - const edits = [ - { oldText: 'line2', newText: 'modified line2' } - ]; - - mockFs.rename.mockResolvedValueOnce(undefined); - - const result = await applyFileEdits('/test/file.txt', edits, false); - - expect(result).toContain('modified line2'); - // Should write to temporary file then rename - expect(mockFs.writeFile).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.txt\.[a-f0-9]+\.tmp$/), - 'line1\nmodified line2\nline3\n', - 'utf-8' - ); - expect(mockFs.rename).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.txt\.[a-f0-9]+\.tmp$/), - '/test/file.txt' - ); - }); - - it('treats dollar signs in replacement text literally', async () => { - const edits = [ - { oldText: 'line2', newText: "price=$$; match=$&; before=$`; after=$'" } - ]; - - mockFs.rename.mockResolvedValueOnce(undefined); - - await applyFileEdits('/test/file.txt', edits, false); - - expect(mockFs.writeFile).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.txt\.[a-f0-9]+\.tmp$/), - "line1\nprice=$$; match=$&; before=$`; after=$'\nline3\n", - 'utf-8' - ); - }); - - it('handles dry run mode', async () => { - const edits = [ - { oldText: 'line2', newText: 'modified line2' } - ]; - - const result = await applyFileEdits('/test/file.txt', edits, true); - - expect(result).toContain('modified line2'); - expect(mockFs.writeFile).not.toHaveBeenCalled(); - }); - - it('applies multiple edits sequentially', async () => { - const edits = [ - { oldText: 'line1', newText: 'first line' }, - { oldText: 'line3', newText: 'third line' } - ]; - - mockFs.rename.mockResolvedValueOnce(undefined); - - await applyFileEdits('/test/file.txt', edits, false); - - expect(mockFs.writeFile).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.txt\.[a-f0-9]+\.tmp$/), - 'first line\nline2\nthird line\n', - 'utf-8' - ); - expect(mockFs.rename).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.txt\.[a-f0-9]+\.tmp$/), - '/test/file.txt' - ); - }); - - it('handles whitespace-flexible matching', async () => { - mockFs.readFile.mockResolvedValue(' line1\n line2\n line3\n'); - - const edits = [ - { oldText: 'line2', newText: 'modified line2' } - ]; - - mockFs.rename.mockResolvedValueOnce(undefined); - - await applyFileEdits('/test/file.txt', edits, false); - - expect(mockFs.writeFile).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.txt\.[a-f0-9]+\.tmp$/), - ' line1\n modified line2\n line3\n', - 'utf-8' - ); - expect(mockFs.rename).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.txt\.[a-f0-9]+\.tmp$/), - '/test/file.txt' - ); - }); - - it('preserves file permissions after applying edits', async () => { - mockFs.stat.mockResolvedValue({ mode: 0o100755 }); - const edits = [ - { oldText: 'line2', newText: 'modified line2' } - ]; - - mockFs.rename.mockResolvedValueOnce(undefined); - - await applyFileEdits('/test/script.sh', edits, false); - - expect(mockFs.stat).toHaveBeenCalledWith('/test/script.sh'); - expect(mockFs.chmod).toHaveBeenCalledWith('/test/script.sh', 0o755); - }); - - it('does not restore permissions in dry run mode', async () => { - const edits = [ - { oldText: 'line2', newText: 'modified line2' } - ]; - - await applyFileEdits('/test/file.txt', edits, true); - - expect(mockFs.chmod).not.toHaveBeenCalled(); - }); - - it('throws error for non-matching edits', async () => { - const edits = [ - { oldText: 'nonexistent line', newText: 'replacement' } - ]; - - await expect(applyFileEdits('/test/file.txt', edits, false)) - .rejects.toThrow('Could not find exact match for edit'); - }); - - it('handles complex multi-line edits with indentation', async () => { - mockFs.readFile.mockResolvedValue('function test() {\n console.log("hello");\n return true;\n}'); - - const edits = [ - { - oldText: ' console.log("hello");\n return true;', - newText: ' console.log("world");\n console.log("test");\n return false;' - } - ]; - - mockFs.rename.mockResolvedValueOnce(undefined); - - await applyFileEdits('/test/file.js', edits, false); - - expect(mockFs.writeFile).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.js\.[a-f0-9]+\.tmp$/), - 'function test() {\n console.log("world");\n console.log("test");\n return false;\n}', - 'utf-8' - ); - expect(mockFs.rename).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.js\.[a-f0-9]+\.tmp$/), - '/test/file.js' - ); - }); - - it('handles edits with different indentation patterns', async () => { - mockFs.readFile.mockResolvedValue(' if (condition) {\n doSomething();\n }'); - - const edits = [ - { - oldText: 'doSomething();', - newText: 'doSomethingElse();\n doAnotherThing();' - } - ]; - - mockFs.rename.mockResolvedValueOnce(undefined); - - await applyFileEdits('/test/file.js', edits, false); - - expect(mockFs.writeFile).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.js\.[a-f0-9]+\.tmp$/), - ' if (condition) {\n doSomethingElse();\n doAnotherThing();\n }', - 'utf-8' - ); - expect(mockFs.rename).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.js\.[a-f0-9]+\.tmp$/), - '/test/file.js' - ); - }); - - it('handles CRLF line endings in file content', async () => { - mockFs.readFile.mockResolvedValue('line1\r\nline2\r\nline3\r\n'); - - const edits = [ - { oldText: 'line2', newText: 'modified line2' } - ]; - - mockFs.rename.mockResolvedValueOnce(undefined); - - await applyFileEdits('/test/file.txt', edits, false); - - expect(mockFs.writeFile).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.txt\.[a-f0-9]+\.tmp$/), - 'line1\nmodified line2\nline3\n', - 'utf-8' - ); - expect(mockFs.rename).toHaveBeenCalledWith( - expect.stringMatching(/\/test\/file\.txt\.[a-f0-9]+\.tmp$/), - '/test/file.txt' - ); - }); - }); - - describe('tailFile', () => { - it('handles empty files', async () => { - mockFs.stat.mockResolvedValue({ size: 0 } as any); - - const result = await tailFile('/test/empty.txt', 5); - - expect(result).toBe(''); - expect(mockFs.open).not.toHaveBeenCalled(); - }); - - it('calls stat to check file size', async () => { - mockFs.stat.mockResolvedValue({ size: 100 } as any); - - // Mock file handle with proper typing - const mockFileHandle = { - read: vi.fn(), - close: vi.fn() - } as any; - - mockFileHandle.read.mockResolvedValue({ bytesRead: 0 }); - mockFileHandle.close.mockResolvedValue(undefined); - - mockFs.open.mockResolvedValue(mockFileHandle); - - await tailFile('/test/file.txt', 2); - - expect(mockFs.stat).toHaveBeenCalledWith('/test/file.txt'); - expect(mockFs.open).toHaveBeenCalledWith('/test/file.txt', 'r'); - }); - - it('handles files with content and returns last lines', async () => { - mockFs.stat.mockResolvedValue({ size: 50 } as any); - - const mockFileHandle = { - read: vi.fn(), - close: vi.fn() - } as any; - - // Simulate reading file content in chunks - mockFileHandle.read - .mockResolvedValueOnce({ bytesRead: 20, buffer: Buffer.from('line3\nline4\nline5\n') }) - .mockResolvedValueOnce({ bytesRead: 0 }); - mockFileHandle.close.mockResolvedValue(undefined); - - mockFs.open.mockResolvedValue(mockFileHandle); - - const result = await tailFile('/test/file.txt', 2); - - expect(mockFileHandle.close).toHaveBeenCalled(); - }); - - it('preserves UTF-8 characters split across chunk boundaries', async () => { - const content = Buffer.concat([ - Buffer.from('discard\n'), - Buffer.from('็•Œ'), - Buffer.alloc(1017, 'a'), - Buffer.from('\nlast'), - ]); - const mockFileHandle = createMockFileHandle(content); - - mockFs.stat.mockResolvedValue({ size: content.length } as any); - mockFs.open.mockResolvedValue(mockFileHandle); - - const result = await tailFile('/test/file.txt', 2); - - expect(result).toBe(`็•Œ${'a'.repeat(1017)}\nlast`); - }); - - it('handles read errors gracefully', async () => { - mockFs.stat.mockResolvedValue({ size: 100 } as any); - - const mockFileHandle = { - read: vi.fn(), - close: vi.fn() - } as any; - - mockFileHandle.read.mockResolvedValue({ bytesRead: 0 }); - mockFileHandle.close.mockResolvedValue(undefined); - - mockFs.open.mockResolvedValue(mockFileHandle); - - await tailFile('/test/file.txt', 5); - - expect(mockFileHandle.close).toHaveBeenCalled(); - }); - }); - - describe('headFile', () => { - it('opens file for reading', async () => { - // Mock file handle with proper typing - const mockFileHandle = { - read: vi.fn(), - close: vi.fn() - } as any; - - mockFileHandle.read.mockResolvedValue({ bytesRead: 0 }); - mockFileHandle.close.mockResolvedValue(undefined); - - mockFs.open.mockResolvedValue(mockFileHandle); - - await headFile('/test/file.txt', 2); - - expect(mockFs.open).toHaveBeenCalledWith('/test/file.txt', 'r'); - }); - - it('handles files with content and returns first lines', async () => { - const mockFileHandle = { - read: vi.fn(), - close: vi.fn() - } as any; - - // Simulate reading file content with newlines - mockFileHandle.read - .mockResolvedValueOnce({ bytesRead: 20, buffer: Buffer.from('line1\nline2\nline3\n') }) - .mockResolvedValueOnce({ bytesRead: 0 }); - mockFileHandle.close.mockResolvedValue(undefined); - - mockFs.open.mockResolvedValue(mockFileHandle); - - const result = await headFile('/test/file.txt', 2); - - expect(mockFileHandle.close).toHaveBeenCalled(); - }); - - it('preserves UTF-8 characters split across chunk boundaries', async () => { - const content = Buffer.concat([ - Buffer.alloc(1023, 'a'), - Buffer.from('็•Œ\nsecond'), - ]); - const mockFileHandle = createMockFileHandle(content); - - mockFs.open.mockResolvedValue(mockFileHandle); - - const result = await headFile('/test/file.txt', 1); - - expect(result).toBe(`${'a'.repeat(1023)}็•Œ`); - }); - - it('handles files with leftover content', async () => { - const mockFileHandle = { - read: vi.fn(), - close: vi.fn() - } as any; - - // Simulate reading file content without final newline - mockFileHandle.read - .mockResolvedValueOnce({ bytesRead: 15, buffer: Buffer.from('line1\nline2\nend') }) - .mockResolvedValueOnce({ bytesRead: 0 }); - mockFileHandle.close.mockResolvedValue(undefined); - - mockFs.open.mockResolvedValue(mockFileHandle); - - const result = await headFile('/test/file.txt', 5); - - expect(mockFileHandle.close).toHaveBeenCalled(); - }); - - it('handles reaching requested line count', async () => { - const mockFileHandle = { - read: vi.fn(), - close: vi.fn() - } as any; - - // Simulate reading exactly the requested number of lines - mockFileHandle.read - .mockResolvedValueOnce({ bytesRead: 12, buffer: Buffer.from('line1\nline2\n') }) - .mockResolvedValueOnce({ bytesRead: 0 }); - mockFileHandle.close.mockResolvedValue(undefined); - - mockFs.open.mockResolvedValue(mockFileHandle); - - const result = await headFile('/test/file.txt', 2); - - expect(mockFileHandle.close).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/src/filesystem/__tests__/nested-parents.test.ts b/src/filesystem/__tests__/nested-parents.test.ts deleted file mode 100644 index 854dc44..0000000 --- a/src/filesystem/__tests__/nested-parents.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import fs from 'fs/promises'; -import os from 'os'; -import path from 'path'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { setAllowedDirectories, validatePath } from '../lib.js'; - -// Regression coverage for #4629: validatePath must accept a path whose -// ancestors are missing several levels deep, so create_directory can mkdir -p. -describe('validatePath with multiple missing ancestors', () => { - let allowedDir: string; - let outsideDir: string; - - beforeEach(async () => { - allowedDir = await fs.realpath(await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-nested-allowed-'))); - outsideDir = await fs.realpath(await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-nested-outside-'))); - setAllowedDirectories([allowedDir]); - }); - - afterEach(async () => { - setAllowedDirectories([]); - await fs.rm(allowedDir, { recursive: true, force: true }); - await fs.rm(outsideDir, { recursive: true, force: true }); - }); - - it('returns the full path when several ancestors do not exist', async () => { - const requested = path.join(allowedDir, 'a', 'b', 'c'); - await expect(validatePath(requested)).resolves.toBe(requested); - }); - - it('rejects when the nearest existing ancestor is a symlink out of the allowed tree', async () => { - await fs.symlink(outsideDir, path.join(allowedDir, 'link'), 'junction'); - await expect(validatePath(path.join(allowedDir, 'link', 'a', 'b'))) - .rejects.toThrow('Access denied'); - }); -}); diff --git a/src/filesystem/__tests__/path-utils.test.ts b/src/filesystem/__tests__/path-utils.test.ts deleted file mode 100644 index 3f60723..0000000 --- a/src/filesystem/__tests__/path-utils.test.ts +++ /dev/null @@ -1,382 +0,0 @@ -import { describe, it, expect, afterEach } from 'vitest'; -import { normalizePath, expandHome, convertToWindowsPath } from '../path-utils.js'; - -describe('Path Utilities', () => { - describe('convertToWindowsPath', () => { - it('leaves Unix paths unchanged', () => { - expect(convertToWindowsPath('/usr/local/bin')) - .toBe('/usr/local/bin'); - expect(convertToWindowsPath('/home/user/some path')) - .toBe('/home/user/some path'); - }); - - it('never converts WSL paths (they work correctly in WSL with Node.js fs)', () => { - // WSL paths should NEVER be converted, regardless of platform - // They are valid Linux paths that work with Node.js fs operations inside WSL - expect(convertToWindowsPath('/mnt/c/NS/MyKindleContent')) - .toBe('/mnt/c/NS/MyKindleContent'); - expect(convertToWindowsPath('/mnt/d/Documents')) - .toBe('/mnt/d/Documents'); - }); - - it('converts Unix-style Windows paths only on Windows platform', () => { - // On Windows, /c/ style paths should be converted - if (process.platform === 'win32') { - expect(convertToWindowsPath('/c/NS/MyKindleContent')) - .toBe('C:\\NS\\MyKindleContent'); - } else { - // On Linux, leave them unchanged - expect(convertToWindowsPath('/c/NS/MyKindleContent')) - .toBe('/c/NS/MyKindleContent'); - } - }); - - it('leaves Windows paths unchanged but ensures backslashes', () => { - expect(convertToWindowsPath('C:\\NS\\MyKindleContent')) - .toBe('C:\\NS\\MyKindleContent'); - expect(convertToWindowsPath('C:/NS/MyKindleContent')) - .toBe('C:\\NS\\MyKindleContent'); - }); - - it('handles Windows paths with spaces', () => { - expect(convertToWindowsPath('C:\\Program Files\\Some App')) - .toBe('C:\\Program Files\\Some App'); - expect(convertToWindowsPath('C:/Program Files/Some App')) - .toBe('C:\\Program Files\\Some App'); - }); - - it('handles drive letter paths based on platform', () => { - // WSL paths should never be converted - expect(convertToWindowsPath('/mnt/d/some/path')) - .toBe('/mnt/d/some/path'); - - if (process.platform === 'win32') { - // On Windows, Unix-style paths like /d/ should be converted - expect(convertToWindowsPath('/d/some/path')) - .toBe('D:\\some\\path'); - } else { - // On Linux, /d/ is just a regular Unix path - expect(convertToWindowsPath('/d/some/path')) - .toBe('/d/some/path'); - } - }); - }); - - describe('normalizePath', () => { - it('preserves Unix paths', () => { - expect(normalizePath('/usr/local/bin')) - .toBe('/usr/local/bin'); - expect(normalizePath('/home/user/some path')) - .toBe('/home/user/some path'); - expect(normalizePath('"/usr/local/some app/"')) - .toBe('/usr/local/some app'); - expect(normalizePath('/usr/local//bin/app///')) - .toBe('/usr/local/bin/app'); - expect(normalizePath('/')) - .toBe('/'); - expect(normalizePath('///')) - .toBe('/'); - }); - - it('removes surrounding quotes', () => { - expect(normalizePath('"C:\\NS\\My Kindle Content"')) - .toBe('C:\\NS\\My Kindle Content'); - }); - - it('normalizes backslashes', () => { - expect(normalizePath('C:\\\\NS\\\\MyKindleContent')) - .toBe('C:\\NS\\MyKindleContent'); - }); - - it('converts forward slashes to backslashes on Windows', () => { - expect(normalizePath('C:/NS/MyKindleContent')) - .toBe('C:\\NS\\MyKindleContent'); - }); - - it('always preserves WSL paths (they work correctly in WSL)', () => { - // WSL paths should ALWAYS be preserved, regardless of platform - // This is the fix for issue #2795 - expect(normalizePath('/mnt/c/NS/MyKindleContent')) - .toBe('/mnt/c/NS/MyKindleContent'); - expect(normalizePath('/mnt/d/Documents')) - .toBe('/mnt/d/Documents'); - }); - - it('handles Unix-style Windows paths', () => { - // On Windows, /c/ paths should be converted - if (process.platform === 'win32') { - expect(normalizePath('/c/NS/MyKindleContent')) - .toBe('C:\\NS\\MyKindleContent'); - } else if (process.platform === 'linux') { - // On Linux, /c/ is just a regular Unix path - expect(normalizePath('/c/NS/MyKindleContent')) - .toBe('/c/NS/MyKindleContent'); - } - }); - - it('handles paths with spaces and mixed slashes', () => { - expect(normalizePath('C:/NS/My Kindle Content')) - .toBe('C:\\NS\\My Kindle Content'); - // WSL paths should always be preserved - expect(normalizePath('/mnt/c/NS/My Kindle Content')) - .toBe('/mnt/c/NS/My Kindle Content'); - expect(normalizePath('C:\\Program Files (x86)\\App Name')) - .toBe('C:\\Program Files (x86)\\App Name'); - expect(normalizePath('"C:\\Program Files\\App Name"')) - .toBe('C:\\Program Files\\App Name'); - expect(normalizePath(' C:\\Program Files\\App Name ')) - .toBe('C:\\Program Files\\App Name'); - }); - - it('preserves spaces in all path formats', () => { - // WSL paths should always be preserved - expect(normalizePath('/mnt/c/Program Files/App Name')) - .toBe('/mnt/c/Program Files/App Name'); - - if (process.platform === 'win32') { - // On Windows, Unix-style paths like /c/ should be converted - expect(normalizePath('/c/Program Files/App Name')) - .toBe('C:\\Program Files\\App Name'); - } else { - // On Linux, /c/ is just a regular Unix path - expect(normalizePath('/c/Program Files/App Name')) - .toBe('/c/Program Files/App Name'); - } - expect(normalizePath('C:/Program Files/App Name')) - .toBe('C:\\Program Files\\App Name'); - }); - - it('handles special characters in paths', () => { - // Test ampersand in path - expect(normalizePath('C:\\NS\\Sub&Folder')) - .toBe('C:\\NS\\Sub&Folder'); - expect(normalizePath('C:/NS/Sub&Folder')) - .toBe('C:\\NS\\Sub&Folder'); - // WSL paths should always be preserved - expect(normalizePath('/mnt/c/NS/Sub&Folder')) - .toBe('/mnt/c/NS/Sub&Folder'); - - // Test tilde in path (short names in Windows) - expect(normalizePath('C:\\NS\\MYKIND~1')) - .toBe('C:\\NS\\MYKIND~1'); - expect(normalizePath('/Users/NEMANS~1/FOLDER~2/SUBFO~1/Public/P12PST~1')) - .toBe('/Users/NEMANS~1/FOLDER~2/SUBFO~1/Public/P12PST~1'); - - // Test other special characters - expect(normalizePath('C:\\Path with #hash')) - .toBe('C:\\Path with #hash'); - expect(normalizePath('C:\\Path with (parentheses)')) - .toBe('C:\\Path with (parentheses)'); - expect(normalizePath('C:\\Path with [brackets]')) - .toBe('C:\\Path with [brackets]'); - expect(normalizePath('C:\\Path with @at+plus$dollar%percent')) - .toBe('C:\\Path with @at+plus$dollar%percent'); - }); - - it('capitalizes lowercase drive letters for Windows paths', () => { - expect(normalizePath('c:/windows/system32')) - .toBe('C:\\windows\\system32'); - // WSL paths should always be preserved - expect(normalizePath('/mnt/d/my/folder')) - .toBe('/mnt/d/my/folder'); - - if (process.platform === 'win32') { - // On Windows, Unix-style paths should be converted and capitalized - expect(normalizePath('/e/another/folder')) - .toBe('E:\\another\\folder'); - } else { - // On Linux, /e/ is just a regular Unix path - expect(normalizePath('/e/another/folder')) - .toBe('/e/another/folder'); - } - }); - - it('handles UNC paths correctly', () => { - // UNC paths should preserve the leading double backslash - const uncPath = '\\\\SERVER\\share\\folder'; - expect(normalizePath(uncPath)).toBe('\\\\SERVER\\share\\folder'); - - // Test UNC path with double backslashes that need normalization - const uncPathWithDoubles = '\\\\\\\\SERVER\\\\share\\\\folder'; - expect(normalizePath(uncPathWithDoubles)).toBe('\\\\SERVER\\share\\folder'); - }); - - it('returns normalized non-Windows/WSL/Unix-style Windows paths as is after basic normalization', () => { - // A path that looks somewhat absolute but isn't a drive or recognized Unix root for Windows conversion - // These paths should be preserved as-is (not converted to Windows C:\ format or WSL format) - const otherAbsolutePath = '\\someserver\\share\\file'; - expect(normalizePath(otherAbsolutePath)).toBe(otherAbsolutePath); - }); - }); - - describe('expandHome', () => { - it('expands ~ to home directory', () => { - const result = expandHome('~/test'); - expect(result).toContain('test'); - expect(result).not.toContain('~'); - }); - - it('expands bare ~ to home directory', () => { - const result = expandHome('~'); - expect(result).not.toContain('~'); - expect(result.length).toBeGreaterThan(0); - }); - - it('leaves other paths unchanged', () => { - expect(expandHome('C:/test')).toBe('C:/test'); - }); - }); - - describe('WSL path handling (issue #2795 fix)', () => { - // Save original platform - const originalPlatform = process.platform; - - afterEach(() => { - // Restore platform after each test - Object.defineProperty(process, 'platform', { - value: originalPlatform, - writable: true, - configurable: true - }); - }); - - it('should NEVER convert WSL paths - they work correctly in WSL with Node.js fs', () => { - // The key insight: When running `wsl npx ...`, Node.js runs INSIDE WSL (process.platform === 'linux') - // and /mnt/c/ paths work correctly with Node.js fs operations in that environment. - // Converting them to C:\ format breaks fs operations because Windows paths don't work inside WSL. - - // Mock Linux platform (inside WSL) - Object.defineProperty(process, 'platform', { - value: 'linux', - writable: true, - configurable: true - }); - - // WSL paths should NOT be converted, even inside WSL - expect(normalizePath('/mnt/c/Users/username/folder')) - .toBe('/mnt/c/Users/username/folder'); - - expect(normalizePath('/mnt/d/Documents/project')) - .toBe('/mnt/d/Documents/project'); - }); - - it('should also preserve WSL paths when running on Windows', () => { - // Mock Windows platform - Object.defineProperty(process, 'platform', { - value: 'win32', - writable: true, - configurable: true - }); - - // WSL paths should still be preserved (though they wouldn't be accessible from Windows Node.js) - expect(normalizePath('/mnt/c/Users/username/folder')) - .toBe('/mnt/c/Users/username/folder'); - - expect(normalizePath('/mnt/d/Documents/project')) - .toBe('/mnt/d/Documents/project'); - }); - - it('should convert Unix-style Windows paths (/c/) only when running on Windows (win32)', () => { - // Mock process.platform to be 'win32' (Windows) - Object.defineProperty(process, 'platform', { - value: 'win32', - writable: true, - configurable: true - }); - - // Unix-style Windows paths like /c/ should be converted on Windows - expect(normalizePath('/c/Users/username/folder')) - .toBe('C:\\Users\\username\\folder'); - - expect(normalizePath('/d/Documents/project')) - .toBe('D:\\Documents\\project'); - }); - - it('should NOT convert Unix-style paths (/c/) when running inside WSL (linux)', () => { - // Mock process.platform to be 'linux' (WSL/Linux) - Object.defineProperty(process, 'platform', { - value: 'linux', - writable: true, - configurable: true - }); - - // When on Linux, /c/ is just a regular Unix directory, not a drive letter - expect(normalizePath('/c/some/path')) - .toBe('/c/some/path'); - - expect(normalizePath('/d/another/path')) - .toBe('/d/another/path'); - }); - - it('should preserve regular Unix paths on all platforms', () => { - // Test on Linux - Object.defineProperty(process, 'platform', { - value: 'linux', - writable: true, - configurable: true - }); - - expect(normalizePath('/home/user/documents')) - .toBe('/home/user/documents'); - - expect(normalizePath('/var/log/app')) - .toBe('/var/log/app'); - - // Test on Windows (though these paths wouldn't work on Windows) - Object.defineProperty(process, 'platform', { - value: 'win32', - writable: true, - configurable: true - }); - - expect(normalizePath('/home/user/documents')) - .toBe('/home/user/documents'); - - expect(normalizePath('/var/log/app')) - .toBe('/var/log/app'); - }); - - it('reproduces exact scenario from issue #2795', () => { - // Simulate running inside WSL: wsl npx @modelcontextprotocol/server-filesystem /mnt/c/Users/username/folder - Object.defineProperty(process, 'platform', { - value: 'linux', - writable: true, - configurable: true - }); - - // This is the exact path from the issue - const inputPath = '/mnt/c/Users/username/folder'; - const result = normalizePath(inputPath); - - // Should NOT convert to C:\Users\username\folder - expect(result).toBe('/mnt/c/Users/username/folder'); - expect(result).not.toContain('C:'); - expect(result).not.toContain('\\'); - }); - - it('normalizes bare Windows drive letters to the drive root on Windows', () => { - Object.defineProperty(process, 'platform', { - value: 'win32', - writable: true, - configurable: true - }); - - expect(normalizePath('C:')).toBe('C:\\'); - expect(normalizePath('d:')).toBe('D:\\'); - }); - - it('should handle relative path slash conversion based on platform', () => { - // This test verifies platform-specific behavior naturally without mocking - // On Windows: forward slashes converted to backslashes - // On Linux/Unix: forward slashes preserved - const relativePath = 'some/relative/path'; - const result = normalizePath(relativePath); - - if (originalPlatform === 'win32') { - expect(result).toBe('some\\relative\\path'); - } else { - expect(result).toBe('some/relative/path'); - } - }); - }); -}); diff --git a/src/filesystem/__tests__/path-validation.test.ts b/src/filesystem/__tests__/path-validation.test.ts deleted file mode 100644 index 81ad247..0000000 --- a/src/filesystem/__tests__/path-validation.test.ts +++ /dev/null @@ -1,1000 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import * as path from 'path'; -import * as fs from 'fs/promises'; -import * as os from 'os'; -import { isPathWithinAllowedDirectories } from '../path-validation.js'; - -/** - * Check if the current environment supports symlink creation - */ -async function checkSymlinkSupport(): Promise { - const testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'symlink-test-')); - try { - const targetFile = path.join(testDir, 'target.txt'); - const linkFile = path.join(testDir, 'link.txt'); - - await fs.writeFile(targetFile, 'test'); - await fs.symlink(targetFile, linkFile); - - // If we get here, symlinks are supported - return true; - } catch (error) { - // EPERM indicates no symlink permissions - if ((error as NodeJS.ErrnoException).code === 'EPERM') { - return false; - } - // Other errors might indicate a real problem - throw error; - } finally { - await fs.rm(testDir, { recursive: true, force: true }); - } -} - -// Global variable to store symlink support status -let symlinkSupported: boolean | null = null; - -/** - * Get cached symlink support status, checking once per test run - */ -async function getSymlinkSupport(): Promise { - if (symlinkSupported === null) { - symlinkSupported = await checkSymlinkSupport(); - if (!symlinkSupported) { - console.log('\nโš ๏ธ Symlink tests will be skipped - symlink creation not supported in this environment'); - console.log(' On Windows, enable Developer Mode or run as Administrator to enable symlink tests'); - } - } - return symlinkSupported; -} - -describe('Path Validation', () => { - it('allows exact directory match', () => { - const allowed = ['/home/user/project']; - expect(isPathWithinAllowedDirectories('/home/user/project', allowed)).toBe(true); - }); - - it('allows subdirectories', () => { - const allowed = ['/home/user/project']; - expect(isPathWithinAllowedDirectories('/home/user/project/src', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/src/index.js', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/deeply/nested/file.txt', allowed)).toBe(true); - }); - - it('blocks similar directory names (prefix vulnerability)', () => { - const allowed = ['/home/user/project']; - expect(isPathWithinAllowedDirectories('/home/user/project2', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project_backup', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project-old', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/projectile', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project.bak', allowed)).toBe(false); - }); - - it('blocks paths outside allowed directories', () => { - const allowed = ['/home/user/project']; - expect(isPathWithinAllowedDirectories('/home/user/other', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/etc/passwd', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/', allowed)).toBe(false); - }); - - it('handles multiple allowed directories', () => { - const allowed = ['/home/user/project1', '/home/user/project2']; - expect(isPathWithinAllowedDirectories('/home/user/project1/src', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project2/src', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project3', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project1_backup', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project2-old', allowed)).toBe(false); - }); - - it('blocks parent and sibling directories', () => { - const allowed = ['/test/allowed']; - - // Parent directory - expect(isPathWithinAllowedDirectories('/test', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/', allowed)).toBe(false); - - // Sibling with common prefix - expect(isPathWithinAllowedDirectories('/test/allowed_sibling', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/test/allowed2', allowed)).toBe(false); - }); - - it('handles paths with special characters', () => { - const allowed = ['/home/user/my-project (v2)']; - - expect(isPathWithinAllowedDirectories('/home/user/my-project (v2)', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/my-project (v2)/src', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/my-project (v2)_backup', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/my-project', allowed)).toBe(false); - }); - - describe('Input validation', () => { - it('rejects empty inputs', () => { - const allowed = ['/home/user/project']; - - expect(isPathWithinAllowedDirectories('', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project', [])).toBe(false); - }); - - it('handles trailing separators correctly', () => { - const allowed = ['/home/user/project']; - - // Path with trailing separator should still match - expect(isPathWithinAllowedDirectories('/home/user/project/', allowed)).toBe(true); - - // Allowed directory with trailing separator - const allowedWithSep = ['/home/user/project/']; - expect(isPathWithinAllowedDirectories('/home/user/project', allowedWithSep)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/', allowedWithSep)).toBe(true); - - // Should still block similar names with or without trailing separators - expect(isPathWithinAllowedDirectories('/home/user/project2', allowedWithSep)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project2', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project2/', allowed)).toBe(false); - }); - - it('skips empty directory entries in allowed list', () => { - const allowed = ['', '/home/user/project', '']; - expect(isPathWithinAllowedDirectories('/home/user/project', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/src', allowed)).toBe(true); - - // Should still validate properly with empty entries - expect(isPathWithinAllowedDirectories('/home/user/other', allowed)).toBe(false); - }); - - it('handles Windows paths with trailing separators', () => { - if (path.sep === '\\') { - const allowed = ['C:\\Users\\project']; - - // Path with trailing separator - expect(isPathWithinAllowedDirectories('C:\\Users\\project\\', allowed)).toBe(true); - - // Allowed with trailing separator - const allowedWithSep = ['C:\\Users\\project\\']; - expect(isPathWithinAllowedDirectories('C:\\Users\\project', allowedWithSep)).toBe(true); - expect(isPathWithinAllowedDirectories('C:\\Users\\project\\', allowedWithSep)).toBe(true); - - // Should still block similar names - expect(isPathWithinAllowedDirectories('C:\\Users\\project2\\', allowed)).toBe(false); - } - }); - }); - - describe('Error handling', () => { - it('normalizes relative paths to absolute', () => { - const allowed = [process.cwd()]; - - // Relative paths get normalized to absolute paths based on cwd - expect(isPathWithinAllowedDirectories('relative/path', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('./file', allowed)).toBe(true); - - // Parent directory references that escape allowed directory - const parentAllowed = ['/home/user/project']; - expect(isPathWithinAllowedDirectories('../parent', parentAllowed)).toBe(false); - }); - - it('returns false for relative paths in allowed directories', () => { - const badAllowed = ['relative/path', '/some/other/absolute/path']; - - // Relative paths in allowed dirs are normalized to absolute based on cwd - // The normalized 'relative/path' won't match our test path - expect(isPathWithinAllowedDirectories('/some/other/absolute/path/file', badAllowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/absolute/path/file', badAllowed)).toBe(false); - }); - - it('handles null and undefined inputs gracefully', () => { - const allowed = ['/home/user/project']; - - // Should return false, not crash - expect(isPathWithinAllowedDirectories(null as any, allowed)).toBe(false); - expect(isPathWithinAllowedDirectories(undefined as any, allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/path', null as any)).toBe(false); - expect(isPathWithinAllowedDirectories('/path', undefined as any)).toBe(false); - }); - }); - - describe('Unicode and special characters', () => { - it('handles unicode characters in paths', () => { - const allowed = ['/home/user/cafรฉ']; - - expect(isPathWithinAllowedDirectories('/home/user/cafรฉ', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/cafรฉ/file', allowed)).toBe(true); - - // Different unicode representation won't match (not normalized) - const decomposed = '/home/user/cafe\u0301'; // e + combining accent - expect(isPathWithinAllowedDirectories(decomposed, allowed)).toBe(false); - }); - - it('handles paths with spaces correctly', () => { - const allowed = ['/home/user/my project']; - - expect(isPathWithinAllowedDirectories('/home/user/my project', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/my project/file', allowed)).toBe(true); - - // Partial matches should fail - expect(isPathWithinAllowedDirectories('/home/user/my', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/my proj', allowed)).toBe(false); - }); - }); - - describe('Overlapping allowed directories', () => { - it('handles nested allowed directories correctly', () => { - const allowed = ['/home', '/home/user', '/home/user/project']; - - // All paths under /home are allowed - expect(isPathWithinAllowedDirectories('/home/anything', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/anything', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/anything', allowed)).toBe(true); - - // First match wins (most permissive) - expect(isPathWithinAllowedDirectories('/home/other/deep/path', allowed)).toBe(true); - }); - - it('handles root directory as allowed', () => { - const allowed = ['/']; - - // Everything is allowed under root (dangerous configuration) - expect(isPathWithinAllowedDirectories('/', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/any/path', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/etc/passwd', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/secret', allowed)).toBe(true); - - // But only on the same filesystem root - if (path.sep === '\\') { - expect(isPathWithinAllowedDirectories('D:\\other', ['/'])).toBe(false); - } - }); - }); - - describe('Cross-platform behavior', () => { - it('handles Windows-style paths on Windows', () => { - if (path.sep === '\\') { - const allowed = ['C:\\Users\\project']; - expect(isPathWithinAllowedDirectories('C:\\Users\\project', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('C:\\Users\\project\\src', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('C:\\Users\\project2', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('C:\\Users\\project_backup', allowed)).toBe(false); - } - }); - - it('handles Unix-style paths on Unix', () => { - if (path.sep === '/') { - const allowed = ['/home/user/project']; - expect(isPathWithinAllowedDirectories('/home/user/project', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/src', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project2', allowed)).toBe(false); - } - }); - }); - - describe('Validation Tests - Path Traversal', () => { - it('blocks path traversal attempts', () => { - const allowed = ['/home/user/project']; - - // Basic traversal attempts - expect(isPathWithinAllowedDirectories('/home/user/project/../../../etc/passwd', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project/../../other', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project/../project2', allowed)).toBe(false); - - // Mixed traversal with valid segments - expect(isPathWithinAllowedDirectories('/home/user/project/src/../../project2', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project/./../../other', allowed)).toBe(false); - - // Multiple traversal sequences - expect(isPathWithinAllowedDirectories('/home/user/project/../project/../../../etc', allowed)).toBe(false); - }); - - it('blocks traversal in allowed directories', () => { - const allowed = ['/home/user/project/../safe']; - - // The allowed directory itself should be normalized and safe - expect(isPathWithinAllowedDirectories('/home/user/safe/file', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/file', allowed)).toBe(false); - }); - - it('handles complex traversal patterns', () => { - const allowed = ['/home/user/project']; - - // Double dots in filenames (not traversal) - these normalize to paths within allowed dir - expect(isPathWithinAllowedDirectories('/home/user/project/..test', allowed)).toBe(true); // Not traversal - expect(isPathWithinAllowedDirectories('/home/user/project/test..', allowed)).toBe(true); // Not traversal - expect(isPathWithinAllowedDirectories('/home/user/project/te..st', allowed)).toBe(true); // Not traversal - - // Actual traversal - expect(isPathWithinAllowedDirectories('/home/user/project/../test', allowed)).toBe(false); // Is traversal - goes to /home/user/test - - // Edge case: /home/user/project/.. normalizes to /home/user (parent dir) - expect(isPathWithinAllowedDirectories('/home/user/project/..', allowed)).toBe(false); // Goes to parent - }); - }); - - describe('Validation Tests - Null Bytes', () => { - it('rejects paths with null bytes', () => { - const allowed = ['/home/user/project']; - - expect(isPathWithinAllowedDirectories('/home/user/project\x00/etc/passwd', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project/test\x00.txt', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('\x00/home/user/project', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project/\x00', allowed)).toBe(false); - }); - - it('rejects allowed directories with null bytes', () => { - const allowed = ['/home/user/project\x00']; - - expect(isPathWithinAllowedDirectories('/home/user/project', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project/file', allowed)).toBe(false); - }); - }); - - describe('Validation Tests - Special Characters', () => { - it('allows percent signs in filenames', () => { - const allowed = ['/home/user/project']; - - // Percent is a valid filename character - expect(isPathWithinAllowedDirectories('/home/user/project/report_50%.pdf', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/Q1_25%_growth', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/%41', allowed)).toBe(true); // File named %41 - - // URL encoding is NOT decoded by path.normalize, so these are just odd filenames - expect(isPathWithinAllowedDirectories('/home/user/project/%2e%2e', allowed)).toBe(true); // File named "%2e%2e" - expect(isPathWithinAllowedDirectories('/home/user/project/file%20name', allowed)).toBe(true); // File with %20 in name - }); - - it('handles percent signs in allowed directories', () => { - const allowed = ['/home/user/project%20files']; - - // This is a directory literally named "project%20files" - expect(isPathWithinAllowedDirectories('/home/user/project%20files/test', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project files/test', allowed)).toBe(false); // Different dir - }); - }); - - describe('Path Normalization', () => { - it('normalizes paths before comparison', () => { - const allowed = ['/home/user/project']; - - // Trailing slashes - expect(isPathWithinAllowedDirectories('/home/user/project/', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project//', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project///', allowed)).toBe(true); - - // Current directory references - expect(isPathWithinAllowedDirectories('/home/user/project/./src', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/./project/src', allowed)).toBe(true); - - // Multiple slashes - expect(isPathWithinAllowedDirectories('/home/user/project//src//file', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home//user//project//src', allowed)).toBe(true); - - // Should still block outside paths - expect(isPathWithinAllowedDirectories('/home/user//project2', allowed)).toBe(false); - }); - - it('handles mixed separators correctly', () => { - if (path.sep === '\\') { - const allowed = ['C:\\Users\\project']; - - // Mixed separators should be normalized - expect(isPathWithinAllowedDirectories('C:/Users/project', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('C:\\Users/project\\src', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('C:/Users\\project/src', allowed)).toBe(true); - } - }); - }); - - describe('Edge Cases', () => { - it('rejects non-string inputs safely', () => { - const allowed = ['/home/user/project']; - - expect(isPathWithinAllowedDirectories(123 as any, allowed)).toBe(false); - expect(isPathWithinAllowedDirectories({} as any, allowed)).toBe(false); - expect(isPathWithinAllowedDirectories([] as any, allowed)).toBe(false); - expect(isPathWithinAllowedDirectories(null as any, allowed)).toBe(false); - expect(isPathWithinAllowedDirectories(undefined as any, allowed)).toBe(false); - - // Non-string in allowed directories - expect(isPathWithinAllowedDirectories('/home/user/project', [123 as any])).toBe(false); - expect(isPathWithinAllowedDirectories('/home/user/project', [{} as any])).toBe(false); - }); - - it('handles very long paths', () => { - const allowed = ['/home/user/project']; - - // Create a very long path that's still valid - const longSubPath = 'a/'.repeat(1000) + 'file.txt'; - expect(isPathWithinAllowedDirectories(`/home/user/project/${longSubPath}`, allowed)).toBe(true); - - // Very long path that escapes - const escapePath = 'a/'.repeat(1000) + '../'.repeat(1001) + 'etc/passwd'; - expect(isPathWithinAllowedDirectories(`/home/user/project/${escapePath}`, allowed)).toBe(false); - }); - }); - - describe('Additional Coverage', () => { - it('handles allowed directories with traversal that normalizes safely', () => { - // These allowed dirs contain traversal but normalize to valid paths - const allowed = ['/home/user/../user/project']; - - // Should normalize to /home/user/project and work correctly - expect(isPathWithinAllowedDirectories('/home/user/project/file', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/other', allowed)).toBe(false); - }); - - it('handles symbolic dots in filenames', () => { - const allowed = ['/home/user/project']; - - // Single and double dots as actual filenames (not traversal) - expect(isPathWithinAllowedDirectories('/home/user/project/.', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('/home/user/project/..', allowed)).toBe(false); // This normalizes to parent - expect(isPathWithinAllowedDirectories('/home/user/project/...', allowed)).toBe(true); // Three dots is a valid filename - expect(isPathWithinAllowedDirectories('/home/user/project/....', allowed)).toBe(true); // Four dots is a valid filename - }); - - it('handles UNC paths on Windows', () => { - if (path.sep === '\\') { - const allowed = ['\\\\server\\share\\project']; - - expect(isPathWithinAllowedDirectories('\\\\server\\share\\project', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('\\\\server\\share\\project\\file', allowed)).toBe(true); - expect(isPathWithinAllowedDirectories('\\\\server\\share\\other', allowed)).toBe(false); - expect(isPathWithinAllowedDirectories('\\\\other\\share\\project', allowed)).toBe(false); - } - }); - }); - - describe('Symlink Tests', () => { - let testDir: string; - let allowedDir: string; - let forbiddenDir: string; - - beforeEach(async () => { - testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fs-error-test-')); - allowedDir = path.join(testDir, 'allowed'); - forbiddenDir = path.join(testDir, 'forbidden'); - - await fs.mkdir(allowedDir, { recursive: true }); - await fs.mkdir(forbiddenDir, { recursive: true }); - }); - - afterEach(async () => { - await fs.rm(testDir, { recursive: true, force: true }); - }); - - it('validates symlink handling', async () => { - // Test with symlinks - try { - const linkPath = path.join(allowedDir, 'bad-link'); - const targetPath = path.join(forbiddenDir, 'target.txt'); - - await fs.writeFile(targetPath, 'content'); - await fs.symlink(targetPath, linkPath); - - // In real implementation, this would throw with the resolved path - const realPath = await fs.realpath(linkPath); - const allowed = [allowedDir]; - - // Symlink target should be outside allowed directory - expect(isPathWithinAllowedDirectories(realPath, allowed)).toBe(false); - } catch (error) { - // Skip if no symlink permissions - } - }); - - it('handles non-existent paths correctly', async () => { - const newFilePath = path.join(allowedDir, 'subdir', 'newfile.txt'); - - // Parent directory doesn't exist - try { - await fs.access(newFilePath); - } catch (error) { - expect((error as NodeJS.ErrnoException).code).toBe('ENOENT'); - } - - // After creating parent, validation should work - await fs.mkdir(path.dirname(newFilePath), { recursive: true }); - const allowed = [allowedDir]; - expect(isPathWithinAllowedDirectories(newFilePath, allowed)).toBe(true); - }); - - // Test path resolution consistency for symlinked files - it('validates symlinked files consistently between path and resolved forms', async () => { - try { - // Setup: Create target file in forbidden area - const targetFile = path.join(forbiddenDir, 'target.txt'); - await fs.writeFile(targetFile, 'TARGET_CONTENT'); - - // Create symlink inside allowed directory pointing to forbidden file - const symlinkPath = path.join(allowedDir, 'link-to-target.txt'); - await fs.symlink(targetFile, symlinkPath); - - // The symlink path itself passes validation (looks like it's in allowed dir) - expect(isPathWithinAllowedDirectories(symlinkPath, [allowedDir])).toBe(true); - - // But the resolved path should fail validation - const resolvedPath = await fs.realpath(symlinkPath); - expect(isPathWithinAllowedDirectories(resolvedPath, [allowedDir])).toBe(false); - - // Verify the resolved path goes to the forbidden location (normalize both paths for macOS temp dirs) - expect(await fs.realpath(resolvedPath)).toBe(await fs.realpath(targetFile)); - } catch (error) { - // Skip if no symlink permissions on the system - if ((error as NodeJS.ErrnoException).code !== 'EPERM') { - throw error; - } - } - }); - - // Test allowed directory resolution behavior - it('validates paths correctly when allowed directory is resolved from symlink', async () => { - try { - // Setup: Create the actual target directory with content - const actualTargetDir = path.join(testDir, 'actual-target'); - await fs.mkdir(actualTargetDir, { recursive: true }); - const targetFile = path.join(actualTargetDir, 'file.txt'); - await fs.writeFile(targetFile, 'FILE_CONTENT'); - - // Setup: Create symlink directory that points to target - const symlinkDir = path.join(testDir, 'symlink-dir'); - await fs.symlink(actualTargetDir, symlinkDir); - - // Simulate resolved allowed directory (what the server startup should do) - const resolvedAllowedDir = await fs.realpath(symlinkDir); - const resolvedTargetDir = await fs.realpath(actualTargetDir); - expect(resolvedAllowedDir).toBe(resolvedTargetDir); - - // Test 1: File access through original symlink path should pass validation with resolved allowed dir - const fileViaSymlink = path.join(symlinkDir, 'file.txt'); - const resolvedFile = await fs.realpath(fileViaSymlink); - expect(isPathWithinAllowedDirectories(resolvedFile, [resolvedAllowedDir])).toBe(true); - - // Test 2: File access through resolved path should also pass validation - const fileViaResolved = path.join(resolvedTargetDir, 'file.txt'); - expect(isPathWithinAllowedDirectories(fileViaResolved, [resolvedAllowedDir])).toBe(true); - - // Test 3: Demonstrate inconsistent behavior with unresolved allowed directories - // If allowed dirs were not resolved (storing symlink paths instead): - const unresolvedAllowedDirs = [symlinkDir]; - // This validation would incorrectly fail for the same content: - expect(isPathWithinAllowedDirectories(resolvedFile, unresolvedAllowedDirs)).toBe(false); - - } catch (error) { - // Skip if no symlink permissions on the system - if ((error as NodeJS.ErrnoException).code !== 'EPERM') { - throw error; - } - } - }); - - // Test for macOS /tmp -> /private/tmp symlink issue (GitHub issue #3253) - // When allowed directories include BOTH original and resolved paths, - // paths through either form should be accepted - it('allows paths through both original and resolved symlink directories', async () => { - try { - // Setup: Create the actual target directory with content - const actualTargetDir = path.join(testDir, 'actual-target'); - await fs.mkdir(actualTargetDir, { recursive: true }); - const targetFile = path.join(actualTargetDir, 'file.txt'); - await fs.writeFile(targetFile, 'FILE_CONTENT'); - - // Setup: Create symlink directory that points to target (simulates /tmp -> /private/tmp) - const symlinkDir = path.join(testDir, 'symlink-dir'); - await fs.symlink(actualTargetDir, symlinkDir); - - // Get the resolved path - const resolvedDir = await fs.realpath(symlinkDir); - - // THE FIX: Store BOTH original symlink path AND resolved path in allowed directories - // This is what the server should do during startup to fix issue #3253 - const allowedDirsWithBoth = [symlinkDir, resolvedDir]; - - // Test 1: Path through original symlink should pass validation - // (e.g., user requests /tmp/file.txt when /tmp is in allowed dirs) - const fileViaSymlink = path.join(symlinkDir, 'file.txt'); - expect(isPathWithinAllowedDirectories(fileViaSymlink, allowedDirsWithBoth)).toBe(true); - - // Test 2: Path through resolved directory should also pass validation - // (e.g., user requests /private/tmp/file.txt) - const fileViaResolved = path.join(resolvedDir, 'file.txt'); - expect(isPathWithinAllowedDirectories(fileViaResolved, allowedDirsWithBoth)).toBe(true); - - // Test 3: The resolved path of the symlink file should also pass - const resolvedFile = await fs.realpath(fileViaSymlink); - expect(isPathWithinAllowedDirectories(resolvedFile, allowedDirsWithBoth)).toBe(true); - - // Verify both paths point to the same actual file - expect(resolvedFile).toBe(await fs.realpath(fileViaResolved)); - - } catch (error) { - // Skip if no symlink permissions on the system - if ((error as NodeJS.ErrnoException).code !== 'EPERM') { - throw error; - } - } - }); - - it('resolves nested symlink chains completely', async () => { - try { - // Setup: Create target file in forbidden area - const actualTarget = path.join(forbiddenDir, 'target-file.txt'); - await fs.writeFile(actualTarget, 'FINAL_CONTENT'); - - // Create chain of symlinks: allowedFile -> link2 -> link1 -> actualTarget - const link1 = path.join(testDir, 'intermediate-link1'); - const link2 = path.join(testDir, 'intermediate-link2'); - const allowedFile = path.join(allowedDir, 'seemingly-safe-file'); - - await fs.symlink(actualTarget, link1); - await fs.symlink(link1, link2); - await fs.symlink(link2, allowedFile); - - // The allowed file path passes basic validation - expect(isPathWithinAllowedDirectories(allowedFile, [allowedDir])).toBe(true); - - // But complete resolution reveals the forbidden target - const fullyResolvedPath = await fs.realpath(allowedFile); - expect(isPathWithinAllowedDirectories(fullyResolvedPath, [allowedDir])).toBe(false); - expect(await fs.realpath(fullyResolvedPath)).toBe(await fs.realpath(actualTarget)); - - } catch (error) { - // Skip if no symlink permissions on the system - if ((error as NodeJS.ErrnoException).code !== 'EPERM') { - throw error; - } - } - }); - }); - - describe('Path Validation Race Condition Tests', () => { - let testDir: string; - let allowedDir: string; - let forbiddenDir: string; - let targetFile: string; - let testPath: string; - - beforeEach(async () => { - testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'race-test-')); - allowedDir = path.join(testDir, 'allowed'); - forbiddenDir = path.join(testDir, 'outside'); - targetFile = path.join(forbiddenDir, 'target.txt'); - testPath = path.join(allowedDir, 'test.txt'); - - await fs.mkdir(allowedDir, { recursive: true }); - await fs.mkdir(forbiddenDir, { recursive: true }); - await fs.writeFile(targetFile, 'ORIGINAL CONTENT', 'utf-8'); - }); - - afterEach(async () => { - await fs.rm(testDir, { recursive: true, force: true }); - }); - - it('validates non-existent file paths based on parent directory', async () => { - const allowed = [allowedDir]; - - expect(isPathWithinAllowedDirectories(testPath, allowed)).toBe(true); - await expect(fs.access(testPath)).rejects.toThrow(); - - const parentDir = path.dirname(testPath); - expect(isPathWithinAllowedDirectories(parentDir, allowed)).toBe(true); - }); - - it('demonstrates symlink race condition allows writing outside allowed directories', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping symlink race condition test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - - await expect(fs.access(testPath)).rejects.toThrow(); - expect(isPathWithinAllowedDirectories(testPath, allowed)).toBe(true); - - await fs.symlink(targetFile, testPath); - await fs.writeFile(testPath, 'MODIFIED CONTENT', 'utf-8'); - - const targetContent = await fs.readFile(targetFile, 'utf-8'); - expect(targetContent).toBe('MODIFIED CONTENT'); - - const resolvedPath = await fs.realpath(testPath); - expect(isPathWithinAllowedDirectories(resolvedPath, allowed)).toBe(false); - }); - - it('shows timing differences between validation approaches', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping timing validation test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - - const validation1 = isPathWithinAllowedDirectories(testPath, allowed); - expect(validation1).toBe(true); - - await fs.symlink(targetFile, testPath); - - const resolvedPath = await fs.realpath(testPath); - const validation2 = isPathWithinAllowedDirectories(resolvedPath, allowed); - expect(validation2).toBe(false); - - expect(validation1).not.toBe(validation2); - }); - - it('validates directory creation timing', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping directory creation timing test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - const testDir = path.join(allowedDir, 'newdir'); - - expect(isPathWithinAllowedDirectories(testDir, allowed)).toBe(true); - - await fs.symlink(forbiddenDir, testDir); - - expect(isPathWithinAllowedDirectories(testDir, allowed)).toBe(true); - - const resolved = await fs.realpath(testDir); - expect(isPathWithinAllowedDirectories(resolved, allowed)).toBe(false); - }); - - it('demonstrates exclusive file creation behavior', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping exclusive file creation test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - - await fs.symlink(targetFile, testPath); - - await expect(fs.open(testPath, 'wx')).rejects.toThrow(/EEXIST/); - - await fs.writeFile(testPath, 'NEW CONTENT', 'utf-8'); - const targetContent = await fs.readFile(targetFile, 'utf-8'); - expect(targetContent).toBe('NEW CONTENT'); - }); - - it('should use resolved parent paths for non-existent files', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping resolved parent paths test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - - const symlinkDir = path.join(allowedDir, 'link'); - await fs.symlink(forbiddenDir, symlinkDir); - - const fileThroughSymlink = path.join(symlinkDir, 'newfile.txt'); - - expect(fileThroughSymlink.startsWith(allowedDir)).toBe(true); - - const parentDir = path.dirname(fileThroughSymlink); - const resolvedParent = await fs.realpath(parentDir); - expect(isPathWithinAllowedDirectories(resolvedParent, allowed)).toBe(false); - - const expectedSafePath = path.join(resolvedParent, path.basename(fileThroughSymlink)); - expect(isPathWithinAllowedDirectories(expectedSafePath, allowed)).toBe(false); - }); - - it('demonstrates parent directory symlink traversal', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping parent directory symlink traversal test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - const deepPath = path.join(allowedDir, 'sub1', 'sub2', 'file.txt'); - - expect(isPathWithinAllowedDirectories(deepPath, allowed)).toBe(true); - - const sub1Path = path.join(allowedDir, 'sub1'); - await fs.symlink(forbiddenDir, sub1Path); - - await fs.mkdir(path.join(sub1Path, 'sub2'), { recursive: true }); - await fs.writeFile(deepPath, 'CONTENT', 'utf-8'); - - const realPath = await fs.realpath(deepPath); - const realAllowedDir = await fs.realpath(allowedDir); - const realForbiddenDir = await fs.realpath(forbiddenDir); - - expect(realPath.startsWith(realAllowedDir)).toBe(false); - expect(realPath.startsWith(realForbiddenDir)).toBe(true); - }); - - it('should prevent race condition between validatePath and file operation', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping race condition prevention test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - const racePath = path.join(allowedDir, 'race-file.txt'); - const targetFile = path.join(forbiddenDir, 'target.txt'); - - await fs.writeFile(targetFile, 'ORIGINAL CONTENT', 'utf-8'); - - // Path validation would pass (file doesn't exist, parent is in allowed dir) - expect(await fs.access(racePath).then(() => false).catch(() => true)).toBe(true); - expect(isPathWithinAllowedDirectories(racePath, allowed)).toBe(true); - - // Race condition: symlink created after validation but before write - await fs.symlink(targetFile, racePath); - - // With exclusive write flag, write should fail on symlink - await expect( - fs.writeFile(racePath, 'NEW CONTENT', { encoding: 'utf-8', flag: 'wx' }) - ).rejects.toThrow(/EEXIST/); - - // Verify content unchanged - const targetContent = await fs.readFile(targetFile, 'utf-8'); - expect(targetContent).toBe('ORIGINAL CONTENT'); - - // The symlink exists but write was blocked - const actualWritePath = await fs.realpath(racePath); - expect(actualWritePath).toBe(await fs.realpath(targetFile)); - expect(isPathWithinAllowedDirectories(actualWritePath, allowed)).toBe(false); - }); - - it('should allow overwrites to legitimate files within allowed directories', async () => { - const allowed = [allowedDir]; - const legitFile = path.join(allowedDir, 'legit-file.txt'); - - // Create a legitimate file - await fs.writeFile(legitFile, 'ORIGINAL', 'utf-8'); - - // Opening with w should work for legitimate files - const fd = await fs.open(legitFile, 'w'); - try { - await fd.write('UPDATED', 0, 'utf-8'); - } finally { - await fd.close(); - } - - const content = await fs.readFile(legitFile, 'utf-8'); - expect(content).toBe('UPDATED'); - }); - - it('should handle symlinks that point within allowed directories', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping symlinks within allowed directories test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - const targetFile = path.join(allowedDir, 'target.txt'); - const symlinkPath = path.join(allowedDir, 'symlink.txt'); - - // Create target file within allowed directory - await fs.writeFile(targetFile, 'TARGET CONTENT', 'utf-8'); - - // Create symlink pointing to allowed file - await fs.symlink(targetFile, symlinkPath); - - // Opening symlink with w follows it to the target - const fd = await fs.open(symlinkPath, 'w'); - try { - await fd.write('UPDATED VIA SYMLINK', 0, 'utf-8'); - } finally { - await fd.close(); - } - - // Both symlink and target should show updated content - const symlinkContent = await fs.readFile(symlinkPath, 'utf-8'); - const targetContent = await fs.readFile(targetFile, 'utf-8'); - expect(symlinkContent).toBe('UPDATED VIA SYMLINK'); - expect(targetContent).toBe('UPDATED VIA SYMLINK'); - }); - - it('should prevent overwriting files through symlinks pointing outside allowed directories', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping symlink overwrite prevention test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - const legitFile = path.join(allowedDir, 'existing.txt'); - const targetFile = path.join(forbiddenDir, 'target.txt'); - - // Create a legitimate file first - await fs.writeFile(legitFile, 'LEGIT CONTENT', 'utf-8'); - - // Create target file in forbidden directory - await fs.writeFile(targetFile, 'FORBIDDEN CONTENT', 'utf-8'); - - // Now replace the legitimate file with a symlink to forbidden location - await fs.unlink(legitFile); - await fs.symlink(targetFile, legitFile); - - // Simulate the server's validation logic - const stats = await fs.lstat(legitFile); - expect(stats.isSymbolicLink()).toBe(true); - - const realPath = await fs.realpath(legitFile); - expect(isPathWithinAllowedDirectories(realPath, allowed)).toBe(false); - - // With atomic rename, symlinks are replaced not followed - // So this test now demonstrates the protection - - // Verify content remains unchanged - const targetContent = await fs.readFile(targetFile, 'utf-8'); - expect(targetContent).toBe('FORBIDDEN CONTENT'); - }); - - it('demonstrates race condition in read operations', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping race condition in read operations test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - const legitFile = path.join(allowedDir, 'readable.txt'); - const secretFile = path.join(forbiddenDir, 'secret.txt'); - - // Create legitimate file - await fs.writeFile(legitFile, 'PUBLIC CONTENT', 'utf-8'); - - // Create secret file in forbidden directory - await fs.writeFile(secretFile, 'SECRET CONTENT', 'utf-8'); - - // Step 1: validatePath would pass for legitimate file - expect(isPathWithinAllowedDirectories(legitFile, allowed)).toBe(true); - - // Step 2: Race condition - replace file with symlink after validation - await fs.unlink(legitFile); - await fs.symlink(secretFile, legitFile); - - // Step 3: Read operation follows symlink to forbidden location - const content = await fs.readFile(legitFile, 'utf-8'); - - // This shows the vulnerability - we read forbidden content - expect(content).toBe('SECRET CONTENT'); - expect(isPathWithinAllowedDirectories(await fs.realpath(legitFile), allowed)).toBe(false); - }); - - it('verifies rename does not follow symlinks', async () => { - const symlinkSupported = await getSymlinkSupport(); - if (!symlinkSupported) { - console.log(' โญ๏ธ Skipping rename symlink test - symlinks not supported'); - return; - } - - const allowed = [allowedDir]; - const tempFile = path.join(allowedDir, 'temp.txt'); - const targetSymlink = path.join(allowedDir, 'target-symlink.txt'); - const forbiddenTarget = path.join(forbiddenDir, 'forbidden-target.txt'); - - // Create forbidden target - await fs.writeFile(forbiddenTarget, 'ORIGINAL CONTENT', 'utf-8'); - - // Create symlink pointing to forbidden location - await fs.symlink(forbiddenTarget, targetSymlink); - - // Write temp file - await fs.writeFile(tempFile, 'NEW CONTENT', 'utf-8'); - - // Rename temp file to symlink path - await fs.rename(tempFile, targetSymlink); - - // Check what happened - const symlinkExists = await fs.lstat(targetSymlink).then(() => true).catch(() => false); - const isSymlink = symlinkExists && (await fs.lstat(targetSymlink)).isSymbolicLink(); - const targetContent = await fs.readFile(targetSymlink, 'utf-8'); - const forbiddenContent = await fs.readFile(forbiddenTarget, 'utf-8'); - - // Rename should replace the symlink with a regular file - expect(isSymlink).toBe(false); - expect(targetContent).toBe('NEW CONTENT'); - expect(forbiddenContent).toBe('ORIGINAL CONTENT'); // Unchanged - }); - }); -}); diff --git a/src/filesystem/__tests__/roots-utils.test.ts b/src/filesystem/__tests__/roots-utils.test.ts deleted file mode 100644 index 1a39483..0000000 --- a/src/filesystem/__tests__/roots-utils.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { getValidRootDirectories } from '../roots-utils.js'; -import { mkdtempSync, rmSync, mkdirSync, writeFileSync, realpathSync } from 'fs'; -import { tmpdir } from 'os'; -import { join } from 'path'; -import type { Root } from '@modelcontextprotocol/sdk/types.js'; - -describe('getValidRootDirectories', () => { - let testDir1: string; - let testDir2: string; - let testDir3: string; - let testFile: string; - - beforeEach(() => { - // Create test directories - testDir1 = realpathSync(mkdtempSync(join(tmpdir(), 'mcp-roots-test1-'))); - testDir2 = realpathSync(mkdtempSync(join(tmpdir(), 'mcp-roots-test2-'))); - testDir3 = realpathSync(mkdtempSync(join(tmpdir(), 'mcp-roots-test3-'))); - - // Create a test file (not a directory) - testFile = join(testDir1, 'test-file.txt'); - writeFileSync(testFile, 'test content'); - }); - - afterEach(() => { - // Cleanup - rmSync(testDir1, { recursive: true, force: true }); - rmSync(testDir2, { recursive: true, force: true }); - rmSync(testDir3, { recursive: true, force: true }); - }); - - describe('valid directory processing', () => { - it('should process all URI formats and edge cases', async () => { - const roots = [ - { uri: `file://${testDir1}`, name: 'File URI' }, - { uri: testDir2, name: 'Plain path' }, - { uri: testDir3 } // Plain path without name property - ]; - - const result = await getValidRootDirectories(roots); - - expect(result).toContain(testDir1); - expect(result).toContain(testDir2); - expect(result).toContain(testDir3); - expect(result).toHaveLength(3); - }); - - it('should normalize complex paths', async () => { - const subDir = join(testDir1, 'subdir'); - mkdirSync(subDir); - - const roots = [ - { uri: `file://${testDir1}/./subdir/../subdir`, name: 'Complex Path' } - ]; - - const result = await getValidRootDirectories(roots); - - expect(result).toHaveLength(1); - expect(result[0]).toBe(subDir); - }); - }); - - describe('error handling', () => { - - it('should handle various error types', async () => { - const nonExistentDir = join(tmpdir(), 'non-existent-directory-12345'); - const invalidPath = '\0invalid\0path'; // Null bytes cause different error types - const roots = [ - { uri: `file://${testDir1}`, name: 'Valid Dir' }, - { uri: `file://${nonExistentDir}`, name: 'Non-existent Dir' }, - { uri: `file://${testFile}`, name: 'File Not Dir' }, - { uri: `file://${invalidPath}`, name: 'Invalid Path' } - ]; - - const result = await getValidRootDirectories(roots); - - expect(result).toContain(testDir1); - expect(result).not.toContain(nonExistentDir); - expect(result).not.toContain(testFile); - expect(result).not.toContain(invalidPath); - expect(result).toHaveLength(1); - }); - }); -}); \ No newline at end of file diff --git a/src/filesystem/__tests__/startup-validation.test.ts b/src/filesystem/__tests__/startup-validation.test.ts deleted file mode 100644 index 3be283d..0000000 --- a/src/filesystem/__tests__/startup-validation.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { spawn } from 'child_process'; -import * as path from 'path'; -import * as fs from 'fs/promises'; -import * as os from 'os'; - -const SERVER_PATH = path.join(__dirname, '..', 'dist', 'index.js'); - -/** - * Spawns the filesystem server with given arguments and returns exit info - */ -async function spawnServer(args: string[], timeoutMs = 2000): Promise<{ exitCode: number | null; stderr: string }> { - return new Promise((resolve) => { - const proc = spawn('node', [SERVER_PATH, ...args], { - stdio: ['pipe', 'pipe', 'pipe'], - }); - - let stderr = ''; - proc.stderr?.on('data', (data) => { - stderr += data.toString(); - }); - - const timeout = setTimeout(() => { - proc.kill('SIGTERM'); - }, timeoutMs); - - proc.on('close', (code) => { - clearTimeout(timeout); - resolve({ exitCode: code, stderr }); - }); - - proc.on('error', (err) => { - clearTimeout(timeout); - resolve({ exitCode: 1, stderr: err.message }); - }); - }); -} - -describe('Startup Directory Validation', () => { - let testDir: string; - let accessibleDir: string; - let accessibleDir2: string; - - beforeEach(async () => { - testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fs-startup-test-')); - accessibleDir = path.join(testDir, 'accessible'); - accessibleDir2 = path.join(testDir, 'accessible2'); - await fs.mkdir(accessibleDir, { recursive: true }); - await fs.mkdir(accessibleDir2, { recursive: true }); - }); - - afterEach(async () => { - await fs.rm(testDir, { recursive: true, force: true }); - }); - - it('should start successfully with all accessible directories', async () => { - const result = await spawnServer([accessibleDir, accessibleDir2]); - // Server starts and runs (we kill it after timeout, so exit code is null or from SIGTERM) - expect(result.stderr).toContain('Secure MCP Filesystem Server running on stdio'); - expect(result.stderr).not.toContain('Error:'); - }); - - it('should skip inaccessible directory and continue with accessible one', async () => { - const nonExistentDir = path.join(testDir, 'non-existent-dir-12345'); - - const result = await spawnServer([nonExistentDir, accessibleDir]); - - // Should warn about inaccessible directory - expect(result.stderr).toContain('Warning: Cannot access directory'); - expect(result.stderr).toContain(nonExistentDir); - - // Should still start successfully - expect(result.stderr).toContain('Secure MCP Filesystem Server running on stdio'); - }); - - it('should exit with error when ALL directories are inaccessible', async () => { - const nonExistent1 = path.join(testDir, 'non-existent-1'); - const nonExistent2 = path.join(testDir, 'non-existent-2'); - - const result = await spawnServer([nonExistent1, nonExistent2]); - - // Should exit with error - expect(result.exitCode).toBe(1); - expect(result.stderr).toContain('Error: None of the specified directories are accessible'); - }); - - it('should warn when path is not a directory', async () => { - const filePath = path.join(testDir, 'not-a-directory.txt'); - await fs.writeFile(filePath, 'content'); - - const result = await spawnServer([filePath, accessibleDir]); - - // Should warn about non-directory - expect(result.stderr).toContain('Warning:'); - expect(result.stderr).toContain('not a directory'); - - // Should still start with the valid directory - expect(result.stderr).toContain('Secure MCP Filesystem Server running on stdio'); - }); -}); diff --git a/src/filesystem/__tests__/structured-content.test.ts b/src/filesystem/__tests__/structured-content.test.ts deleted file mode 100644 index 67da16f..0000000 --- a/src/filesystem/__tests__/structured-content.test.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import * as os from 'os'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; -import { spawn } from 'child_process'; -import { fileURLToPath } from 'url'; - -/** - * Integration tests to verify that tool handlers return structuredContent - * that matches the declared outputSchema. - * - * These tests address issues #3110, #3106, #3093 where tools were returning - * structuredContent: { content: [contentBlock] } (array) instead of - * structuredContent: { content: string } as declared in outputSchema. - */ -describe('structuredContent schema compliance', () => { - let client: Client; - let transport: StdioClientTransport; - let testDir: string; - - beforeEach(async () => { - // Create a temp directory for testing - const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-fs-test-')); - // macOS temp dirs may be symlinked (/var -> /private/var); resolve for server allowlist checks. - testDir = await fs.realpath(tmpDir); - - // Create test files - await fs.writeFile(path.join(testDir, 'test.txt'), 'test content'); - await fs.mkdir(path.join(testDir, 'subdir')); - await fs.writeFile(path.join(testDir, 'subdir', 'nested.txt'), 'nested content'); - - // Start the MCP server - const serverPath = path.resolve(__dirname, '../dist/index.js'); - transport = new StdioClientTransport({ - command: 'node', - args: [serverPath, testDir], - }); - - client = new Client({ - name: 'test-client', - version: '1.0.0', - }, { - capabilities: {} - }); - - await client.connect(transport); - }); - - afterEach(async () => { - await client?.close(); - await fs.rm(testDir, { recursive: true, force: true }); - }); - - describe('directory_tree', () => { - it('should return structuredContent.content as a string, not an array', async () => { - const result = await client.callTool({ - name: 'directory_tree', - arguments: { path: testDir } - }); - - // The result should have structuredContent - expect(result.structuredContent).toBeDefined(); - - // structuredContent.content should be a string (matching outputSchema: { content: z.string() }) - const structuredContent = result.structuredContent as { content: unknown }; - expect(typeof structuredContent.content).toBe('string'); - - // It should NOT be an array - expect(Array.isArray(structuredContent.content)).toBe(false); - - // The content should be valid JSON representing the tree - const treeData = JSON.parse(structuredContent.content as string); - expect(Array.isArray(treeData)).toBe(true); - }); - }); - - describe('list_directory_with_sizes', () => { - it('should return structuredContent.content as a string, not an array', async () => { - const result = await client.callTool({ - name: 'list_directory_with_sizes', - arguments: { path: testDir } - }); - - // The result should have structuredContent - expect(result.structuredContent).toBeDefined(); - - // structuredContent.content should be a string (matching outputSchema: { content: z.string() }) - const structuredContent = result.structuredContent as { content: unknown }; - expect(typeof structuredContent.content).toBe('string'); - - // It should NOT be an array - expect(Array.isArray(structuredContent.content)).toBe(false); - - // The content should contain directory listing info - expect(structuredContent.content).toContain('[FILE]'); - }); - }); - - describe('move_file', () => { - it('should return structuredContent.content as a string, not an array', async () => { - const sourcePath = path.join(testDir, 'test.txt'); - const destPath = path.join(testDir, 'moved.txt'); - - const result = await client.callTool({ - name: 'move_file', - arguments: { - source: sourcePath, - destination: destPath - } - }); - - // The result should have structuredContent - expect(result.structuredContent).toBeDefined(); - - // structuredContent.content should be a string (matching outputSchema: { content: z.string() }) - const structuredContent = result.structuredContent as { content: unknown }; - expect(typeof structuredContent.content).toBe('string'); - - // It should NOT be an array - expect(Array.isArray(structuredContent.content)).toBe(false); - - // The content should contain success message - expect(structuredContent.content).toContain('Successfully moved'); - }); - }); - - describe('list_directory (control - already working)', () => { - it('should return structuredContent.content as a string', async () => { - const result = await client.callTool({ - name: 'list_directory', - arguments: { path: testDir } - }); - - expect(result.structuredContent).toBeDefined(); - - const structuredContent = result.structuredContent as { content: unknown }; - expect(typeof structuredContent.content).toBe('string'); - expect(Array.isArray(structuredContent.content)).toBe(false); - }); - }); - - describe('search_files (control - already working)', () => { - it('should return structuredContent.content as a string', async () => { - const result = await client.callTool({ - name: 'search_files', - arguments: { - path: testDir, - pattern: '*.txt' - } - }); - - expect(result.structuredContent).toBeDefined(); - - const structuredContent = result.structuredContent as { content: unknown }; - expect(typeof structuredContent.content).toBe('string'); - expect(Array.isArray(structuredContent.content)).toBe(false); - }); - }); - - // read_media_file must return a VALID MCP content block. Previously it emitted - // type: "blob" for non-image/audio files, which is not in the MCP content-block - // union (text | image | audio | resource_link | resource) and a strict client - // rejects on schema validation. See issue #4029. - describe('read_media_file (issue #4029)', () => { - // Image/audio inputs already returned valid content types before #4029 (only the - // non-media fallback was the invalid "blob"), so these two cases are "still-works" - // coverage โ€” they round-trip the bytes to catch an encoding regression on the media - // paths. The resource + cafรฉ cases below are the actual #4029 regression guards - // (they fail against the pre-fix "blob" build). - it('returns type "image" for image files, round-tripping the bytes', async () => { - const pngBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); - const pngPath = path.join(testDir, 'pixel.png'); - // Contents aren't validated by the tool (MIME is by extension), so arbitrary bytes are fine. - await fs.writeFile(pngPath, pngBytes); - - const result = await client.callTool({ - name: 'read_media_file', - arguments: { path: pngPath } - }); - - const content = result.content as Array<{ type: string; data?: string; mimeType?: string }>; - expect(Array.isArray(content)).toBe(true); - expect(content).toHaveLength(1); - expect(content[0].type).toBe('image'); - expect(content[0].mimeType).toBe('image/png'); - expect(Buffer.from(content[0].data!, 'base64').equals(pngBytes)).toBe(true); - // structuredContent must MIRROR content. The SDK validates each field independently - // against the outputSchema union (either arm is valid for either field), so schema - // validation alone cannot catch the two drifting apart โ€” only this equality can. - expect(result.structuredContent).toEqual({ content }); - }); - - it('returns type "audio" for audio files, round-tripping the bytes', async () => { - const mp3Bytes = Buffer.from([0x49, 0x44, 0x33, 0x04]); - const mp3Path = path.join(testDir, 'clip.mp3'); - await fs.writeFile(mp3Path, mp3Bytes); - - const result = await client.callTool({ - name: 'read_media_file', - arguments: { path: mp3Path } - }); - - const content = result.content as Array<{ type: string; data?: string; mimeType?: string }>; - expect(content[0].type).toBe('audio'); - expect(content[0].mimeType).toBe('audio/mpeg'); - expect(Buffer.from(content[0].data!, 'base64').equals(mp3Bytes)).toBe(true); - expect(result.structuredContent).toEqual({ content }); // must mirror content (see image case) - }); - - it('returns an embedded resource (never "blob") for non-media binaries, round-tripping the bytes', async () => { - const binBytes = Buffer.from([0x00, 0x01, 0x02, 0x03, 0xff, 0xfe]); - const binPath = path.join(testDir, 'data.bin'); - await fs.writeFile(binPath, binBytes); - - const result = await client.callTool({ - name: 'read_media_file', - arguments: { path: binPath } - }); - - const content = result.content as Array<{ - type: string; - resource?: { uri: string; mimeType: string; blob: string }; - }>; - expect(content).toHaveLength(1); - // ("never blob" is enforced upstream of any assertion here: the SDK client's - // CallToolResultSchema parse rejects a blob block before content is even inspected, - // failing the callTool above โ€” which is exactly how this test fails pre-fix.) - expect(content[0].type).toBe('resource'); - expect(content[0].resource).toBeDefined(); - expect(content[0].resource!.uri.startsWith('file://')).toBe(true); - // Decode the uri back to a path and confirm it is the file actually read โ€” guards a - // refactor that builds the uri from the wrong variable while still emitting a - // well-formed file:// string. (realpath handles the /tmp -> /private/tmp macOS alias.) - expect(fileURLToPath(content[0].resource!.uri)).toBe(await fs.realpath(binPath)); - expect(content[0].resource!.mimeType).toBe('application/octet-stream'); - // The base64 blob must round-trip to the original bytes (data integrity, not just a valid - // shape) โ€” this also subsumes a non-empty check. - expect(Buffer.from(content[0].resource!.blob, 'base64').equals(binBytes)).toBe(true); - expect(result.structuredContent).toEqual({ content }); // must mirror content (see image case) - }); - - it('percent-encodes spaces and non-ASCII chars in the resource uri (pathToFileURL, not a raw file:// concatenation)', async () => { - const fancyPath = path.join(testDir, 'my cafรฉ.bin'); - await fs.writeFile(fancyPath, Buffer.from([0x10, 0x20, 0x30])); - - const result = await client.callTool({ - name: 'read_media_file', - arguments: { path: fancyPath } - }); - - const content = result.content as Array<{ type: string; resource?: { uri: string } }>; - expect(content[0].type).toBe('resource'); - const uri = content[0].resource!.uri; - // pathToFileURL percent-encodes the space (%20) and the non-ASCII "รฉ" (%C3%A9 for NFC, - // or the base "e" + %CC%81 for the NFD form some filesystems store); a raw - // `file://${validPath}` (as in PR #4044) would leave both literal. - expect(uri).toContain('%20'); - expect(uri).not.toContain(' '); - // The non-ASCII "รฉ" must be percent-encoded: %C3%A9 (NFC) or base-"e" + %CC%81 (the NFD - // form some filesystems decompose to). This regex โ€” not a `.not.toContain('รฉ')`, which is - // vacuous on an NFD runner where the precomposed char never appears โ€” is the load-bearing - // assertion that distinguishes pathToFileURL from a raw `file://${path}` concatenation. - expect(uri).toMatch(/%C3%A9|%CC%81/); - expect(result.structuredContent).toEqual({ content }); // must mirror content (see image case) - }); - - it('advertises the widened outputSchema union via tools/list (both branches serialize)', async () => { - const { tools } = await client.listTools(); - const tool = tools.find((t) => t.name === 'read_media_file'); - expect(tool).toBeDefined(); - // The union outputSchema must serialize to JSON Schema โ€” a DIFFERENT SDK path - // (toJsonSchemaCompat / tools-list) than the callTool structuredContent validation the - // other cases exercise โ€” with BOTH branches present. Guards a nested-union-in-array - // serialization quirk that could drop the resource arm from the advertised schema. - const schemaStr = JSON.stringify(tool!.outputSchema); - expect(schemaStr).toMatch(/resource/); - expect(schemaStr).toMatch(/image/); - expect(schemaStr).toMatch(/audio/); - }); - }); -}); diff --git a/src/filesystem/__tests__/unicode-paths.test.ts b/src/filesystem/__tests__/unicode-paths.test.ts deleted file mode 100644 index 2d28ac2..0000000 --- a/src/filesystem/__tests__/unicode-paths.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import fs from 'fs/promises'; -import os from 'os'; -import path from 'path'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { setAllowedDirectories, validatePath } from '../lib.js'; - -describe('Unicode-equivalent filesystem paths', () => { - let testDirectory: string; - - beforeEach(async () => { - testDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-unicode-paths-')); - setAllowedDirectories([testDirectory]); - }); - - afterEach(async () => { - setAllowedDirectories([]); - await fs.rm(testDirectory, { recursive: true, force: true }); - }); - - it('resolves an existing decomposed path from a composed request', async () => { - const onDiskDirectory = 'de\u0301marche'; - const onDiskFile = 're\u0301sume\u0301.txt'; - await fs.mkdir(path.join(testDirectory, onDiskDirectory)); - await fs.writeFile(path.join(testDirectory, onDiskDirectory, onDiskFile), 'content'); - - const resolved = await validatePath(path.join(testDirectory, 'd\u00e9marche', 'r\u00e9sum\u00e9.txt')); - - expect(resolved).toBe(await fs.realpath(path.join(testDirectory, onDiskDirectory, onDiskFile))); - }); - - it('preserves a new basename after resolving a Unicode-equivalent parent', async () => { - const onDiskDirectory = 'de\u0301marche'; - await fs.mkdir(path.join(testDirectory, onDiskDirectory)); - - const resolved = await validatePath(path.join(testDirectory, 'd\u00e9marche', 'new.txt')); - - expect(resolved).toBe(path.join(await fs.realpath(path.join(testDirectory, onDiskDirectory)), 'new.txt')); - }); - - it('rejects ambiguous canonically equivalent entries', async () => { - const composed = 'caf\u00e9'; - const decomposed = 'cafe\u0301'; - await fs.mkdir(path.join(testDirectory, composed)); - await fs.mkdir(path.join(testDirectory, decomposed)); - - await expect(validatePath(path.join(testDirectory, 'cafe\u0341', 'file.txt'))) - .rejects.toThrow('Ambiguous Unicode path component'); - }); -}); diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 51ac523..b4c4e92 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -1,114 +1,104 @@ #!/usr/bin/env node -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { - RootsListChangedNotificationSchema, - type Root, + CallToolRequestSchema, + ListToolsRequestSchema, + ToolSchema, } from "@modelcontextprotocol/sdk/types.js"; import fs from "fs/promises"; -import { createReadStream } from "fs"; import path from "path"; -import { pathToFileURL } from "url"; +import os from 'os'; import { z } from "zod"; -import { minimatch } from "minimatch"; -import { normalizePath, expandHome } from './path-utils.js'; -import { getValidRootDirectories } from './roots-utils.js'; -import { - // Function imports - formatSize, - validatePath, - getFileStats, - readFileContent, - writeFileContent, - moveFile, - searchFilesWithValidation, - applyFileEdits, - tailFile, - headFile, - setAllowedDirectories, -} from './lib.js'; +import { zodToJsonSchema } from "zod-to-json-schema"; // Command line argument parsing const args = process.argv.slice(2); if (args.length === 0) { - console.error("Usage: mcp-server-filesystem [allowed-directory] [additional-directories...]"); - console.error("Note: Allowed directories can be provided via:"); - console.error(" 1. Command-line arguments (shown above)"); - console.error(" 2. MCP roots protocol (if client supports it)"); - console.error("At least one directory must be provided by EITHER method for the server to operate."); -} - -// Store allowed directories in normalized and resolved form -// We store BOTH the original path AND the resolved path to handle symlinks correctly -// This fixes the macOS /tmp -> /private/tmp symlink issue where users specify /tmp -// but the resolved path is /private/tmp -let allowedDirectories = (await Promise.all( - args.map(async (dir) => { - const expanded = expandHome(dir); - const absolute = path.resolve(expanded); - const normalizedOriginal = normalizePath(absolute); - try { - // Security: Resolve symlinks in allowed directories during startup - // This ensures we know the real paths and can validate against them later - const resolved = await fs.realpath(absolute); - const normalizedResolved = normalizePath(resolved); - // Return both original and resolved paths if they differ - // This allows matching against either /tmp or /private/tmp on macOS - if (normalizedOriginal !== normalizedResolved) { - return [normalizedOriginal, normalizedResolved]; - } - return [normalizedResolved]; - } catch (error) { - // If we can't resolve (doesn't exist), use the normalized absolute path - // This allows configuring allowed dirs that will be created later - return [normalizedOriginal]; - } - }) -)).flat(); - -// Filter to only accessible directories, warn about inaccessible ones -const accessibleDirectories: string[] = []; -for (const dir of allowedDirectories) { - try { - const stats = await fs.stat(dir); - if (stats.isDirectory()) { - accessibleDirectories.push(dir); - } else { - console.error(`Warning: ${dir} is not a directory, skipping`); - } - } catch (error) { - console.error(`Warning: Cannot access directory ${dir}, skipping`); - } -} - -// Exit only if ALL paths are inaccessible (and some were specified) -if (accessibleDirectories.length === 0 && allowedDirectories.length > 0) { - console.error("Error: None of the specified directories are accessible"); + console.error("Usage: mcp-server-filesystem [additional-directories...]"); process.exit(1); } -allowedDirectories = accessibleDirectories; +// Normalize all paths consistently +function normalizePath(p: string): string { + return path.normalize(p).toLowerCase(); +} -// Initialize the global allowedDirectories in lib.ts -setAllowedDirectories(allowedDirectories); +function expandHome(filepath: string): string { + if (filepath.startsWith('~/') || filepath === '~') { + return path.join(os.homedir(), filepath.slice(1)); + } + return filepath; +} + +// Store allowed directories in normalized form +const allowedDirectories = args.map(dir => + normalizePath(path.resolve(expandHome(dir))) +); + +// Validate that all directories exist and are accessible +await Promise.all(args.map(async (dir) => { + try { + const stats = await fs.stat(dir); + if (!stats.isDirectory()) { + console.error(`Error: ${dir} is not a directory`); + process.exit(1); + } + } catch (error) { + console.error(`Error accessing directory ${dir}:`, error); + process.exit(1); + } +})); + +// Security utilities +async function validatePath(requestedPath: string): Promise { + const expandedPath = expandHome(requestedPath); + const absolute = path.isAbsolute(expandedPath) + ? path.resolve(expandedPath) + : path.resolve(process.cwd(), expandedPath); + + const normalizedRequested = normalizePath(absolute); + + // Check if path is within allowed directories + const isAllowed = allowedDirectories.some(dir => normalizedRequested.startsWith(dir)); + if (!isAllowed) { + throw new Error(`Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(', ')}`); + } + + // Handle symlinks by checking their real path + try { + const realPath = await fs.realpath(absolute); + const normalizedReal = normalizePath(realPath); + const isRealPathAllowed = allowedDirectories.some(dir => normalizedReal.startsWith(dir)); + if (!isRealPathAllowed) { + throw new Error("Access denied - symlink target outside allowed directories"); + } + return realPath; + } catch (error) { + // For new files that don't exist yet, verify parent directory + const parentDir = path.dirname(absolute); + try { + const realParentPath = await fs.realpath(parentDir); + const normalizedParent = normalizePath(realParentPath); + const isParentAllowed = allowedDirectories.some(dir => normalizedParent.startsWith(dir)); + if (!isParentAllowed) { + throw new Error("Access denied - parent directory outside allowed directories"); + } + return absolute; + } catch { + throw new Error(`Parent directory does not exist: ${parentDir}`); + } + } +} // Schema definitions -const ReadTextFileArgsSchema = z.object({ +const ReadFileArgsSchema = z.object({ path: z.string(), - tail: z.number().optional().describe('If provided, returns only the last N lines of the file'), - head: z.number().optional().describe('If provided, returns only the first N lines of the file') -}); - -const ReadMediaFileArgsSchema = z.object({ - path: z.string() }); const ReadMultipleFilesArgsSchema = z.object({ - paths: z - .array(z.string()) - .min(1, "At least one file path must be provided") - .describe("Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories."), + paths: z.array(z.string()), }); const WriteFileArgsSchema = z.object({ @@ -116,17 +106,6 @@ const WriteFileArgsSchema = z.object({ content: z.string(), }); -const EditOperation = z.object({ - oldText: z.string().describe('Text to search for - must match exactly'), - newText: z.string().describe('Text to replace with') -}); - -const EditFileArgsSchema = z.object({ - path: z.string(), - edits: z.array(EditOperation), - dryRun: z.boolean().default(false).describe('Preview changes using git-style diff format') -}); - const CreateDirectoryArgsSchema = z.object({ path: z.string(), }); @@ -135,16 +114,6 @@ const ListDirectoryArgsSchema = z.object({ path: z.string(), }); -const ListDirectoryWithSizesArgsSchema = z.object({ - path: z.string(), - sortBy: z.enum(['name', 'size']).optional().default('name').describe('Sort entries by name or size'), -}); - -const DirectoryTreeArgsSchema = z.object({ - path: z.string(), - excludePatterns: z.array(z.string()).optional().default([]) -}); - const MoveFileArgsSchema = z.object({ source: z.string(), destination: z.string(), @@ -153,634 +122,326 @@ const MoveFileArgsSchema = z.object({ const SearchFilesArgsSchema = z.object({ path: z.string(), pattern: z.string(), - excludePatterns: z.array(z.string()).optional().default([]) }); const GetFileInfoArgsSchema = z.object({ path: z.string(), }); +const ToolInputSchema = ToolSchema.shape.inputSchema; +type ToolInput = z.infer; + +interface FileInfo { + size: number; + created: Date; + modified: Date; + accessed: Date; + isDirectory: boolean; + isFile: boolean; + permissions: string; +} + // Server setup -const server = new McpServer( +const server = new Server( { name: "secure-filesystem-server", version: "0.2.0", - } + }, + { + capabilities: { + tools: {}, + }, + }, ); -// Reads a file as a stream of buffers, concatenates them, and then encodes -// the result to a Base64 string. This is a memory-efficient way to handle -// binary data from a stream before the final encoding. -async function readFileAsBase64Stream(filePath: string): Promise { - return new Promise((resolve, reject) => { - const stream = createReadStream(filePath); - const chunks: Buffer[] = []; - stream.on('data', (chunk) => { - chunks.push(chunk as Buffer); - }); - stream.on('end', () => { - const finalBuffer = Buffer.concat(chunks); - resolve(finalBuffer.toString('base64')); - }); - stream.on('error', (err) => reject(err)); - }); +// Tool implementations +async function getFileStats(filePath: string): Promise { + const stats = await fs.stat(filePath); + return { + size: stats.size, + created: stats.birthtime, + modified: stats.mtime, + accessed: stats.atime, + isDirectory: stats.isDirectory(), + isFile: stats.isFile(), + permissions: stats.mode.toString(8).slice(-3), + }; } -// Tool registrations +async function searchFiles( + rootPath: string, + pattern: string, +): Promise { + const results: string[] = []; -// read_file (deprecated) and read_text_file -const readTextFileHandler = async (args: z.infer) => { - const validPath = await validatePath(args.path); + async function search(currentPath: string) { + const entries = await fs.readdir(currentPath, { withFileTypes: true }); - if (args.head && args.tail) { - throw new Error("Cannot specify both head and tail parameters simultaneously"); - } + for (const entry of entries) { + const fullPath = path.join(currentPath, entry.name); + + try { + // Validate each path before processing + await validatePath(fullPath); - let content: string; - if (args.tail) { - content = await tailFile(validPath, args.tail); - } else if (args.head) { - content = await headFile(validPath, args.head); - } else { - content = await readFileContent(validPath); - } - - return { - content: [{ type: "text" as const, text: content }], - structuredContent: { content } - }; -}; - -server.registerTool( - "read_file", - { - title: "Read File (Deprecated)", - description: "Read the complete contents of a file as text. DEPRECATED: Use read_text_file instead.", - inputSchema: ReadTextFileArgsSchema.shape, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - readTextFileHandler -); - -server.registerTool( - "read_text_file", - { - title: "Read Text File", - description: - "Read the complete contents of a file from the file system as text. " + - "Handles various text encodings and provides detailed error messages " + - "if the file cannot be read. Use this tool when you need to examine " + - "the contents of a single file. Use the 'head' parameter to read only " + - "the first N lines of a file, or the 'tail' parameter to read only " + - "the last N lines of a file. Operates on the file as text regardless of extension. " + - "Only works within allowed directories.", - inputSchema: { - path: z.string(), - tail: z.number().optional().describe("If provided, returns only the last N lines of the file"), - head: z.number().optional().describe("If provided, returns only the first N lines of the file") - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - readTextFileHandler -); - -server.registerTool( - "read_media_file", - { - title: "Read Media File", - description: - "Read a file and return it as a base64-encoded content block with its MIME type. " + - "Image and audio files are returned as image/audio content; any other file type is " + - "returned as an embedded resource. Only works within allowed directories.", - inputSchema: { - path: z.string() - }, - outputSchema: { - content: z.array(z.union([ - z.object({ - type: z.enum(["image", "audio"]), - data: z.string(), - mimeType: z.string() - }), - z.object({ - type: z.literal("resource"), - resource: z.object({ - uri: z.string(), - // Optional, matching the SDK's BlobResourceContents shape (the handler always sets it). - mimeType: z.string().optional(), - blob: z.string() - }) - }) - ])) - }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - const validPath = await validatePath(args.path); - const extension = path.extname(validPath).toLowerCase(); - const mimeTypes: Record = { - ".png": "image/png", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".gif": "image/gif", - ".webp": "image/webp", - ".bmp": "image/bmp", - ".svg": "image/svg+xml", - ".mp3": "audio/mpeg", - ".wav": "audio/wav", - ".ogg": "audio/ogg", - ".flac": "audio/flac", - }; - const mimeType = mimeTypes[extension] || "application/octet-stream"; - const data = await readFileAsBase64Stream(validPath); - - // Map the MIME type to a valid MCP content block. The spec only allows - // text, image, audio, resource_link, and resource โ€” so non-image/audio - // binaries are returned as an embedded resource (NOT type:"blob", which the - // SDK content-block union rejects on schema validation). - const contentItem = - mimeType.startsWith("image/") - ? { type: "image" as const, data, mimeType } - : mimeType.startsWith("audio/") - ? { type: "audio" as const, data, mimeType } - : { - type: "resource" as const, - resource: { uri: pathToFileURL(validPath).href, mimeType, blob: data } - }; - return { - content: [contentItem], - structuredContent: { content: [contentItem] } - }; - } -); - -server.registerTool( - "read_multiple_files", - { - title: "Read Multiple Files", - description: - "Read the contents of multiple files simultaneously. This is more " + - "efficient than reading files one by one when you need to analyze " + - "or compare multiple files. Each file's content is returned with its " + - "path as a reference. Failed reads for individual files won't stop " + - "the entire operation. Only works within allowed directories.", - inputSchema: { - paths: z.array(z.string()) - .min(1) - .describe("Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories.") - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - const results = await Promise.all( - args.paths.map(async (filePath: string) => { - try { - const validPath = await validatePath(filePath); - const content = await readFileContent(validPath); - return `${filePath}:\n${content}\n`; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - return `${filePath}: Error - ${errorMessage}`; + if (entry.name.toLowerCase().includes(pattern.toLowerCase())) { + results.push(fullPath); } - }), - ); - const text = results.join("\n---\n"); - return { - content: [{ type: "text" as const, text }], - structuredContent: { content: text } - }; - } -); - -server.registerTool( - "write_file", - { - title: "Write File", - description: - "Create a new file or completely overwrite an existing file with new content. " + - "Use with caution as it will overwrite existing files without warning. " + - "Handles text content with proper encoding. Only works within allowed directories.", - inputSchema: { - path: z.string(), - content: z.string() - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - const validPath = await validatePath(args.path); - await writeFileContent(validPath, args.content); - const text = `Successfully wrote to ${args.path}`; - return { - content: [{ type: "text" as const, text }], - structuredContent: { content: text } - }; - } -); - -server.registerTool( - "edit_file", - { - title: "Edit File", - description: - "Make line-based edits to a text file. Each edit replaces exact line sequences " + - "with new content. Returns a git-style diff showing the changes made. " + - "Only works within allowed directories.", - inputSchema: { - path: z.string(), - edits: z.array(z.object({ - oldText: z.string().describe("Text to search for - must match exactly"), - newText: z.string().describe("Text to replace with") - })), - dryRun: z.boolean().default(false).describe("Preview changes using git-style diff format") - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - const validPath = await validatePath(args.path); - const result = await applyFileEdits(validPath, args.edits, args.dryRun); - return { - content: [{ type: "text" as const, text: result }], - structuredContent: { content: result } - }; - } -); - -server.registerTool( - "create_directory", - { - title: "Create Directory", - description: - "Create a new directory or ensure a directory exists. Can create multiple " + - "nested directories in one operation. If the directory already exists, " + - "this operation will succeed silently. Perfect for setting up directory " + - "structures for projects or ensuring required paths exist. Only works within allowed directories.", - inputSchema: { - path: z.string() - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false, openWorldHint: false } - }, - async (args: z.infer) => { - const validPath = await validatePath(args.path); - await fs.mkdir(validPath, { recursive: true }); - const text = `Successfully created directory ${args.path}`; - return { - content: [{ type: "text" as const, text }], - structuredContent: { content: text } - }; - } -); - -server.registerTool( - "list_directory", - { - title: "List Directory", - description: - "Get a detailed listing of all files and directories in a specified path. " + - "Results clearly distinguish between files and directories with [FILE] and [DIR] " + - "prefixes. This tool is essential for understanding directory structure and " + - "finding specific files within a directory. Only works within allowed directories.", - inputSchema: { - path: z.string() - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - const validPath = await validatePath(args.path); - const entries = await fs.readdir(validPath, { withFileTypes: true }); - const formatted = entries - .map((entry) => `${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${entry.name}`) - .join("\n"); - return { - content: [{ type: "text" as const, text: formatted }], - structuredContent: { content: formatted } - }; - } -); - -server.registerTool( - "list_directory_with_sizes", - { - title: "List Directory with Sizes", - description: - "Get a detailed listing of all files and directories in a specified path, including sizes. " + - "Results clearly distinguish between files and directories with [FILE] and [DIR] " + - "prefixes. This tool is useful for understanding directory structure and " + - "finding specific files within a directory. Only works within allowed directories.", - inputSchema: { - path: z.string(), - sortBy: z.enum(["name", "size"]).optional().default("name").describe("Sort entries by name or size") - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - const validPath = await validatePath(args.path); - const entries = await fs.readdir(validPath, { withFileTypes: true }); - - // Get detailed information for each entry - const detailedEntries = await Promise.all( - entries.map(async (entry) => { - const entryPath = path.join(validPath, entry.name); - try { - const stats = await fs.stat(entryPath); - return { - name: entry.name, - isDirectory: entry.isDirectory(), - size: stats.size, - mtime: stats.mtime - }; - } catch (error) { - return { - name: entry.name, - isDirectory: entry.isDirectory(), - size: 0, - mtime: new Date(0) - }; - } - }) - ); - - // Sort entries based on sortBy parameter - const sortedEntries = [...detailedEntries].sort((a, b) => { - if (args.sortBy === 'size') { - return b.size - a.size; // Descending by size - } - // Default sort by name - return a.name.localeCompare(b.name); - }); - - // Format the output - const formattedEntries = sortedEntries.map(entry => - `${entry.isDirectory ? "[DIR]" : "[FILE]"} ${entry.name.padEnd(30)} ${ - entry.isDirectory ? "" : formatSize(entry.size).padStart(10) - }` - ); - - // Add summary - const totalFiles = detailedEntries.filter(e => !e.isDirectory).length; - const totalDirs = detailedEntries.filter(e => e.isDirectory).length; - const totalSize = detailedEntries.reduce((sum, entry) => sum + (entry.isDirectory ? 0 : entry.size), 0); - - const summary = [ - "", - `Total: ${totalFiles} files, ${totalDirs} directories`, - `Combined size: ${formatSize(totalSize)}` - ]; - - const text = [...formattedEntries, ...summary].join("\n"); - const contentBlock = { type: "text" as const, text }; - return { - content: [contentBlock], - structuredContent: { content: text } - }; - } -); - -server.registerTool( - "directory_tree", - { - title: "Directory Tree", - description: - "Get a recursive tree view of files and directories as a JSON structure. " + - "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. " + - "Files have no children array, while directories always have a children array (which may be empty). " + - "The output is formatted with 2-space indentation for readability. Only works within allowed directories.", - inputSchema: { - path: z.string(), - excludePatterns: z.array(z.string()).optional().default([]) - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - interface TreeEntry { - name: string; - type: 'file' | 'directory'; - children?: TreeEntry[]; - } - const rootPath = args.path; - - async function buildTree(currentPath: string, excludePatterns: string[] = []): Promise { - const validPath = await validatePath(currentPath); - const entries = await fs.readdir(validPath, { withFileTypes: true }); - const result: TreeEntry[] = []; - - for (const entry of entries) { - const relativePath = path.relative(rootPath, path.join(currentPath, entry.name)); - const shouldExclude = excludePatterns.some(pattern => { - if (pattern.includes('*')) { - return minimatch(relativePath, pattern, { dot: true }); - } - // For files: match exact name or as part of path - // For directories: match as directory path - return minimatch(relativePath, pattern, { dot: true }) || - minimatch(relativePath, `**/${pattern}`, { dot: true }) || - minimatch(relativePath, `**/${pattern}/**`, { dot: true }); - }); - if (shouldExclude) - continue; - - const entryData: TreeEntry = { - name: entry.name, - type: entry.isDirectory() ? 'directory' : 'file' - }; if (entry.isDirectory()) { - const subPath = path.join(currentPath, entry.name); - entryData.children = await buildTree(subPath, excludePatterns); + await search(fullPath); } - - result.push(entryData); + } catch (error) { + // Skip invalid paths during search + continue; } - - return result; } - - const treeData = await buildTree(rootPath, args.excludePatterns); - const text = JSON.stringify(treeData, null, 2); - const contentBlock = { type: "text" as const, text }; - return { - content: [contentBlock], - structuredContent: { content: text } - }; } -); -server.registerTool( - "move_file", - { - title: "Move File", - description: - "Move or rename files and directories. Can move files between directories " + - "and rename them in a single operation. If the destination exists, the " + - "operation will fail. Works across different directories and can be used " + - "for simple renaming within the same directory. Both source and destination must be within allowed directories.", - inputSchema: { - source: z.string(), - destination: z.string() - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - const validSourcePath = await validatePath(args.source); - const validDestPath = await validatePath(args.destination); - await moveFile(validSourcePath, validDestPath); - const text = `Successfully moved ${args.source} to ${args.destination}`; - const contentBlock = { type: "text" as const, text }; - return { - content: [contentBlock], - structuredContent: { content: text } - }; - } -); - -server.registerTool( - "search_files", - { - title: "Search Files", - description: - "Recursively search for files and directories matching a pattern. " + - "The patterns should be glob-style patterns that match paths relative to the working directory. " + - "Use pattern like '*.ext' to match files in current directory, and '**/*.ext' to match files in all subdirectories. " + - "Returns full paths to all matching items. Great for finding files when you don't know their exact location. " + - "Only searches within allowed directories.", - inputSchema: { - path: z.string(), - pattern: z.string(), - excludePatterns: z.array(z.string()).optional().default([]) - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - const validPath = await validatePath(args.path); - const results = await searchFilesWithValidation(validPath, args.pattern, allowedDirectories, { excludePatterns: args.excludePatterns }); - const text = results.length > 0 ? results.join("\n") : "No matches found"; - return { - content: [{ type: "text" as const, text }], - structuredContent: { content: text } - }; - } -); - -server.registerTool( - "get_file_info", - { - title: "Get File Info", - description: - "Retrieve detailed metadata about a file or directory. Returns comprehensive " + - "information including size, creation time, last modified time, permissions, " + - "and type. This tool is perfect for understanding file characteristics " + - "without reading the actual content. Only works within allowed directories.", - inputSchema: { - path: z.string() - }, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - async (args: z.infer) => { - const validPath = await validatePath(args.path); - const info = await getFileStats(validPath); - const text = Object.entries(info) - .map(([key, value]) => `${key}: ${value}`) - .join("\n"); - return { - content: [{ type: "text" as const, text }], - structuredContent: { content: text } - }; - } -); - -server.registerTool( - "list_allowed_directories", - { - title: "List Allowed Directories", - description: - "Returns the list of directories that this server is allowed to access. " + - "Subdirectories within these allowed directories are also accessible. " + - "Use this to understand which directories and their nested paths are available " + - "before trying to access files.", - inputSchema: {}, - outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true, openWorldHint: false } - }, - async () => { - const text = `Allowed directories:\n${allowedDirectories.join('\n')}`; - return { - content: [{ type: "text" as const, text }], - structuredContent: { content: text } - }; - } -); - -// Updates allowed directories based on MCP client roots -async function updateAllowedDirectoriesFromRoots(requestedRoots: Root[]) { - const validatedRootDirs = await getValidRootDirectories(requestedRoots); - if (validatedRootDirs.length > 0) { - allowedDirectories = [...validatedRootDirs]; - setAllowedDirectories(allowedDirectories); // Update the global state in lib.ts - console.error(`Updated allowed directories from MCP roots: ${validatedRootDirs.length} valid directories`); - } else { - console.error("No valid root directories provided by client"); - } + await search(rootPath); + return results; } -// Handles dynamic roots updates during runtime, when client sends "roots/list_changed" notification, server fetches the updated roots and replaces all allowed directories with the new roots. -server.server.setNotificationHandler(RootsListChangedNotificationSchema, async () => { - try { - // Request the updated roots list from the client - const response = await server.server.listRoots(); - if (response && 'roots' in response) { - await updateAllowedDirectoriesFromRoots(response.roots); - } - } catch (error) { - console.error("Failed to request roots from client:", error instanceof Error ? error.message : String(error)); - } +// Tool handlers +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "read_file", + description: + "Read the complete contents of a file from the file system. " + + "Handles various text encodings and provides detailed error messages " + + "if the file cannot be read. Use this tool when you need to examine " + + "the contents of a single file. Only works within allowed directories.", + inputSchema: zodToJsonSchema(ReadFileArgsSchema) as ToolInput, + }, + { + name: "read_multiple_files", + description: + "Read the contents of multiple files simultaneously. This is more " + + "efficient than reading files one by one when you need to analyze " + + "or compare multiple files. Each file's content is returned with its " + + "path as a reference. Failed reads for individual files won't stop " + + "the entire operation. Only works within allowed directories.", + inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema) as ToolInput, + }, + { + name: "write_file", + description: + "Create a new file or completely overwrite an existing file with new content. " + + "Use with caution as it will overwrite existing files without warning. " + + "Handles text content with proper encoding. Only works within allowed directories.", + inputSchema: zodToJsonSchema(WriteFileArgsSchema) as ToolInput, + }, + { + name: "create_directory", + description: + "Create a new directory or ensure a directory exists. Can create multiple " + + "nested directories in one operation. If the directory already exists, " + + "this operation will succeed silently. Perfect for setting up directory " + + "structures for projects or ensuring required paths exist. Only works within allowed directories.", + inputSchema: zodToJsonSchema(CreateDirectoryArgsSchema) as ToolInput, + }, + { + name: "list_directory", + description: + "Get a detailed listing of all files and directories in a specified path. " + + "Results clearly distinguish between files and directories with [FILE] and [DIR] " + + "prefixes. This tool is essential for understanding directory structure and " + + "finding specific files within a directory. Only works within allowed directories.", + inputSchema: zodToJsonSchema(ListDirectoryArgsSchema) as ToolInput, + }, + { + name: "move_file", + description: + "Move or rename files and directories. Can move files between directories " + + "and rename them in a single operation. If the destination exists, the " + + "operation will fail. Works across different directories and can be used " + + "for simple renaming within the same directory. Both source and destination must be within allowed directories.", + inputSchema: zodToJsonSchema(MoveFileArgsSchema) as ToolInput, + }, + { + name: "search_files", + description: + "Recursively search for files and directories matching a pattern. " + + "Searches through all subdirectories from the starting path. The search " + + "is case-insensitive and matches partial names. Returns full paths to all " + + "matching items. Great for finding files when you don't know their exact location. " + + "Only searches within allowed directories.", + inputSchema: zodToJsonSchema(SearchFilesArgsSchema) as ToolInput, + }, + { + name: "get_file_info", + description: + "Retrieve detailed metadata about a file or directory. Returns comprehensive " + + "information including size, creation time, last modified time, permissions, " + + "and type. This tool is perfect for understanding file characteristics " + + "without reading the actual content. Only works within allowed directories.", + inputSchema: zodToJsonSchema(GetFileInfoArgsSchema) as ToolInput, + }, + { + name: "list_allowed_directories", + description: + "Returns the list of directories that this server is allowed to access. " + + "Use this to understand which directories are available before trying to access files.", + inputSchema: { + type: "object", + properties: {}, + required: [], + }, + }, + ], + }; }); -// Handles post-initialization setup, specifically checking for and fetching MCP roots. -server.server.oninitialized = async () => { - const clientCapabilities = server.server.getClientCapabilities(); - if (clientCapabilities?.roots) { - try { - const response = await server.server.listRoots(); - if (response && 'roots' in response) { - await updateAllowedDirectoriesFromRoots(response.roots); - } else { - console.error("Client returned no roots set, keeping current settings"); +server.setRequestHandler(CallToolRequestSchema, async (request) => { + try { + const { name, arguments: args } = request.params; + + switch (name) { + case "read_file": { + const parsed = ReadFileArgsSchema.safeParse(args); + if (!parsed.success) { + throw new Error(`Invalid arguments for read_file: ${parsed.error}`); + } + const validPath = await validatePath(parsed.data.path); + const content = await fs.readFile(validPath, "utf-8"); + return { + content: [{ type: "text", text: content }], + }; } - } catch (error) { - console.error("Failed to request initial roots from client:", error instanceof Error ? error.message : String(error)); - } - } else { - if (allowedDirectories.length > 0) { - console.error("Client does not support MCP Roots, using allowed directories set from server args:", allowedDirectories); - }else{ - throw new Error(`Server cannot operate: No allowed directories available. Server was started without command-line directories and client either does not support MCP roots protocol or provided empty roots. Please either: 1) Start server with directory arguments, or 2) Use a client that supports MCP roots protocol and provides valid root directories.`); + + case "read_multiple_files": { + const parsed = ReadMultipleFilesArgsSchema.safeParse(args); + if (!parsed.success) { + throw new Error(`Invalid arguments for read_multiple_files: ${parsed.error}`); + } + const results = await Promise.all( + parsed.data.paths.map(async (filePath: string) => { + try { + const validPath = await validatePath(filePath); + const content = await fs.readFile(validPath, "utf-8"); + return `${filePath}:\n${content}\n`; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return `${filePath}: Error - ${errorMessage}`; + } + }), + ); + return { + content: [{ type: "text", text: results.join("\n---\n") }], + }; + } + + case "write_file": { + const parsed = WriteFileArgsSchema.safeParse(args); + if (!parsed.success) { + throw new Error(`Invalid arguments for write_file: ${parsed.error}`); + } + const validPath = await validatePath(parsed.data.path); + await fs.writeFile(validPath, parsed.data.content, "utf-8"); + return { + content: [{ type: "text", text: `Successfully wrote to ${parsed.data.path}` }], + }; + } + + case "create_directory": { + const parsed = CreateDirectoryArgsSchema.safeParse(args); + if (!parsed.success) { + throw new Error(`Invalid arguments for create_directory: ${parsed.error}`); + } + const validPath = await validatePath(parsed.data.path); + await fs.mkdir(validPath, { recursive: true }); + return { + content: [{ type: "text", text: `Successfully created directory ${parsed.data.path}` }], + }; + } + + case "list_directory": { + const parsed = ListDirectoryArgsSchema.safeParse(args); + if (!parsed.success) { + throw new Error(`Invalid arguments for list_directory: ${parsed.error}`); + } + const validPath = await validatePath(parsed.data.path); + const entries = await fs.readdir(validPath, { withFileTypes: true }); + const formatted = entries + .map((entry) => `${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${entry.name}`) + .join("\n"); + return { + content: [{ type: "text", text: formatted }], + }; + } + + case "move_file": { + const parsed = MoveFileArgsSchema.safeParse(args); + if (!parsed.success) { + throw new Error(`Invalid arguments for move_file: ${parsed.error}`); + } + const validSourcePath = await validatePath(parsed.data.source); + const validDestPath = await validatePath(parsed.data.destination); + await fs.rename(validSourcePath, validDestPath); + return { + content: [{ type: "text", text: `Successfully moved ${parsed.data.source} to ${parsed.data.destination}` }], + }; + } + + case "search_files": { + const parsed = SearchFilesArgsSchema.safeParse(args); + if (!parsed.success) { + throw new Error(`Invalid arguments for search_files: ${parsed.error}`); + } + const validPath = await validatePath(parsed.data.path); + const results = await searchFiles(validPath, parsed.data.pattern); + return { + content: [{ type: "text", text: results.length > 0 ? results.join("\n") : "No matches found" }], + }; + } + + case "get_file_info": { + const parsed = GetFileInfoArgsSchema.safeParse(args); + if (!parsed.success) { + throw new Error(`Invalid arguments for get_file_info: ${parsed.error}`); + } + const validPath = await validatePath(parsed.data.path); + const info = await getFileStats(validPath); + return { + content: [{ type: "text", text: Object.entries(info) + .map(([key, value]) => `${key}: ${value}`) + .join("\n") }], + }; + } + + case "list_allowed_directories": { + return { + content: [{ + type: "text", + text: `Allowed directories:\n${allowedDirectories.join('\n')}` + }], + }; + } + + default: + throw new Error(`Unknown tool: ${name}`); } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return { + content: [{ type: "text", text: `Error: ${errorMessage}` }], + isError: true, + }; } -}; +}); // Start server async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Secure MCP Filesystem Server running on stdio"); - if (allowedDirectories.length === 0) { - console.error("Started without allowed directories - waiting for client to provide roots via MCP protocol"); - } + console.error("Allowed directories:", allowedDirectories); } runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); -}); +}); \ No newline at end of file diff --git a/src/filesystem/lib.ts b/src/filesystem/lib.ts deleted file mode 100644 index 6cd2165..0000000 --- a/src/filesystem/lib.ts +++ /dev/null @@ -1,483 +0,0 @@ -import fs from "fs/promises"; -import path from "path"; -import os from 'os'; -import { randomBytes } from 'crypto'; -import { StringDecoder } from 'string_decoder'; -import { diffLines, createTwoFilesPatch } from 'diff'; -import { minimatch } from 'minimatch'; -import { normalizePath, expandHome } from './path-utils.js'; -import { isPathWithinAllowedDirectories } from './path-validation.js'; - -// Global allowed directories - set by the main module -let allowedDirectories: string[] = []; - -// Function to set allowed directories from the main module -export function setAllowedDirectories(directories: string[]): void { - allowedDirectories = [...directories]; -} - -// Function to get current allowed directories -export function getAllowedDirectories(): string[] { - return [...allowedDirectories]; -} - -// Type definitions -interface FileInfo { - size: number; - created: Date; - modified: Date; - accessed: Date; - isDirectory: boolean; - isFile: boolean; - permissions: string; -} - -export interface SearchOptions { - excludePatterns?: string[]; -} - -export interface SearchResult { - path: string; - isDirectory: boolean; -} - -// Pure Utility Functions -export function formatSize(bytes: number): string { - const units = ['B', 'KB', 'MB', 'GB', 'TB']; - if (bytes <= 0) return '0 B'; - - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - - if (i < 0 || i === 0) return `${bytes} ${units[0]}`; - - const unitIndex = Math.min(i, units.length - 1); - return `${(bytes / Math.pow(1024, unitIndex)).toFixed(2)} ${units[unitIndex]}`; -} - -export function normalizeLineEndings(text: string): string { - return text.replace(/\r\n/g, '\n'); -} - -export function createUnifiedDiff(originalContent: string, newContent: string, filepath: string = 'file'): string { - // Ensure consistent line endings for diff - const normalizedOriginal = normalizeLineEndings(originalContent); - const normalizedNew = normalizeLineEndings(newContent); - - return createTwoFilesPatch( - filepath, - filepath, - normalizedOriginal, - normalizedNew, - 'original', - 'modified' - ); -} - -// Helper function to resolve relative paths against allowed directories -function resolveRelativePathAgainstAllowedDirectories(relativePath: string): string { - if (allowedDirectories.length === 0) { - // Fallback to process.cwd() if no allowed directories are set - return path.resolve(process.cwd(), relativePath); - } - - // Try to resolve relative path against each allowed directory - for (const allowedDir of allowedDirectories) { - const candidate = path.resolve(allowedDir, relativePath); - const normalizedCandidate = normalizePath(candidate); - - // Check if the resulting path lies within any allowed directory - if (isPathWithinAllowedDirectories(normalizedCandidate, allowedDirectories)) { - return candidate; - } - } - - // If no valid resolution found, use the first allowed directory as base - // This provides a consistent fallback behavior - return path.resolve(allowedDirectories[0], relativePath); -} - -// Security & Validation Functions -async function resolveUnicodeEquivalentPath(absolutePath: string): Promise { - const allowedDirectory = [...allowedDirectories] - .sort((left, right) => right.length - left.length) - .find(directory => isPathWithinAllowedDirectories(normalizePath(absolutePath), [directory])); - - if (!allowedDirectory) { - return absolutePath; - } - - let currentPath = await fs.realpath(allowedDirectory); - const relativeParts = path.relative(allowedDirectory, absolutePath).split(path.sep).filter(Boolean); - - for (let index = 0; index < relativeParts.length; index++) { - const requestedPart = relativeParts[index]; - const entries = (await fs.readdir(currentPath)) ?? []; - const exactMatch = entries.find(entry => entry === requestedPart); - const equivalentMatches = exactMatch - ? [exactMatch] - : entries.filter(entry => entry.normalize('NFC') === requestedPart.normalize('NFC')); - - if (equivalentMatches.length > 1) { - throw new Error(`Ambiguous Unicode path component: ${requestedPart}`); - } - - if (equivalentMatches.length === 0) { - // Nothing below this point exists yet, so there are no symlinks left to - // resolve. currentPath is already realpath'd and inside an allowed - // directory; append the missing tail so create_directory can mkdir -p it. - return path.join(currentPath, ...relativeParts.slice(index)); - } - - currentPath = await fs.realpath(path.join(currentPath, equivalentMatches[0])); - if (!isPathWithinAllowedDirectories(normalizePath(currentPath), allowedDirectories)) { - throw new Error(`Access denied - symlink target outside allowed directories: ${currentPath} not in ${allowedDirectories.join(', ')}`); - } - } - - return currentPath; -} - -export async function validatePath(requestedPath: string): Promise { - const expandedPath = expandHome(requestedPath); - // Do not silently reinterpret a Windows drive path as a relative POSIX path. - // This would create a literal filename such as `C:\\Users\\...` inside the - // allowed root and report success for the wrong location. - if (process.platform !== 'win32' && /^(?:[A-Za-z]:)(?:[\\/]|$)/.test(expandedPath)) { - throw new Error(`Access denied - Windows-style path received on a POSIX host: ${requestedPath}`); - } - const absolute = path.isAbsolute(expandedPath) - ? path.resolve(expandedPath) - : resolveRelativePathAgainstAllowedDirectories(expandedPath); - - const normalizedRequested = normalizePath(absolute); - - // Security: Check if path is within allowed directories before any file operations - const isAllowed = isPathWithinAllowedDirectories(normalizedRequested, allowedDirectories); - if (!isAllowed) { - throw new Error(`Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(', ')}`); - } - - // Security: Handle symlinks by checking their real path to prevent symlink attacks - // This prevents attackers from creating symlinks that point outside allowed directories - try { - const realPath = await fs.realpath(absolute); - const normalizedReal = normalizePath(realPath); - if (!isPathWithinAllowedDirectories(normalizedReal, allowedDirectories)) { - throw new Error(`Access denied - symlink target outside allowed directories: ${realPath} not in ${allowedDirectories.join(', ')}`); - } - return realPath; - } catch (error) { - // Security: For new files that don't exist yet, verify parent directory - // This ensures we can't create files in unauthorized locations - if ((error as NodeJS.ErrnoException).code === 'ENOENT') { - try { - return await resolveUnicodeEquivalentPath(absolute); - } catch (resolutionError) { - if ((resolutionError as NodeJS.ErrnoException).code === 'ENOENT') { - throw new Error(`Parent directory does not exist: ${path.dirname(absolute)}`); - } - throw resolutionError; - } - } - throw error; - } -} - - -// File Operations -export async function getFileStats(filePath: string): Promise { - const stats = await fs.stat(filePath); - return { - size: stats.size, - created: stats.birthtime, - modified: stats.mtime, - accessed: stats.atime, - isDirectory: stats.isDirectory(), - isFile: stats.isFile(), - permissions: stats.mode.toString(8).slice(-3), - }; -} - -export async function readFileContent(filePath: string, encoding: string = 'utf-8'): Promise { - return await fs.readFile(filePath, encoding as BufferEncoding); -} - -export async function writeFileContent(filePath: string, content: string): Promise { - try { - // Security: 'wx' flag ensures exclusive creation - fails if file/symlink exists, - // preventing writes through pre-existing symlinks - await fs.writeFile(filePath, content, { encoding: "utf-8", flag: 'wx' }); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'EEXIST') { - // Security: Use atomic rename to prevent race conditions where symlinks - // could be created between validation and write. Rename operations - // replace the target file atomically and don't follow symlinks. - const origStats = await fs.stat(filePath); - const tempPath = `${filePath}.${randomBytes(16).toString('hex')}.tmp`; - try { - await fs.writeFile(tempPath, content, 'utf-8'); - await fs.rename(tempPath, filePath); - } catch (renameError) { - try { - await fs.unlink(tempPath); - } catch {} - throw renameError; - } - // Restore original permission bits since the atomic rename replaces the - // inode and the temp file has default (0644) permissions. Mask off the - // file-type bits; POSIX leaves them unspecified for chmod. A chmod - // failure must not fail the write, which has already succeeded. - try { - await fs.chmod(filePath, origStats.mode & 0o777); - } catch {} - } else { - throw error; - } - } -} - - -export async function moveFile(sourcePath: string, destinationPath: string): Promise { - // The move_file tool contract (and README) state the operation fails if the - // destination already exists. fs.rename would silently overwrite it, which is - // a data-loss bug, so reject up front when anything - file, directory, or - // symlink - occupies the target. lstat is used so an existing symlink at the - // destination is detected rather than followed. - try { - await fs.lstat(destinationPath); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') { - await fs.rename(sourcePath, destinationPath); - return; - } - throw error; - } - throw new Error(`Destination already exists: ${destinationPath}`); -} - - -// File Editing Functions -interface FileEdit { - oldText: string; - newText: string; -} - -export async function applyFileEdits( - filePath: string, - edits: FileEdit[], - dryRun: boolean = false -): Promise { - // Read file content and normalize line endings - const content = normalizeLineEndings(await fs.readFile(filePath, 'utf-8')); - - // Apply edits sequentially - let modifiedContent = content; - for (const edit of edits) { - const normalizedOld = normalizeLineEndings(edit.oldText); - const normalizedNew = normalizeLineEndings(edit.newText); - - // If exact match exists, use it - if (modifiedContent.includes(normalizedOld)) { - modifiedContent = modifiedContent.replace(normalizedOld, () => normalizedNew); - continue; - } - - // Otherwise, try line-by-line matching with flexibility for whitespace - const oldLines = normalizedOld.split('\n'); - const contentLines = modifiedContent.split('\n'); - let matchFound = false; - - for (let i = 0; i <= contentLines.length - oldLines.length; i++) { - const potentialMatch = contentLines.slice(i, i + oldLines.length); - - // Compare lines with normalized whitespace - const isMatch = oldLines.every((oldLine, j) => { - const contentLine = potentialMatch[j]; - return oldLine.trim() === contentLine.trim(); - }); - - if (isMatch) { - // Preserve original indentation of first line - const originalIndent = contentLines[i].match(/^\s*/)?.[0] || ''; - const newLines = normalizedNew.split('\n').map((line, j) => { - if (j === 0) return originalIndent + line.trimStart(); - // For subsequent lines, try to preserve relative indentation - const oldIndent = oldLines[j]?.match(/^\s*/)?.[0] || ''; - const newIndent = line.match(/^\s*/)?.[0] || ''; - if (oldIndent && newIndent) { - const relativeIndent = newIndent.length - oldIndent.length; - return originalIndent + ' '.repeat(Math.max(0, relativeIndent)) + line.trimStart(); - } - return line; - }); - - contentLines.splice(i, oldLines.length, ...newLines); - modifiedContent = contentLines.join('\n'); - matchFound = true; - break; - } - } - - if (!matchFound) { - throw new Error(`Could not find exact match for edit:\n${edit.oldText}`); - } - } - - // Create unified diff - const diff = createUnifiedDiff(content, modifiedContent, filePath); - - // Format diff with appropriate number of backticks - let numBackticks = 3; - while (diff.includes('`'.repeat(numBackticks))) { - numBackticks++; - } - const formattedDiff = `${'`'.repeat(numBackticks)}diff\n${diff}${'`'.repeat(numBackticks)}\n\n`; - - if (!dryRun) { - // Security: Use atomic rename to prevent race conditions where symlinks - // could be created between validation and write. Rename operations - // replace the target file atomically and don't follow symlinks. - const origStats = await fs.stat(filePath); - const tempPath = `${filePath}.${randomBytes(16).toString('hex')}.tmp`; - try { - await fs.writeFile(tempPath, modifiedContent, 'utf-8'); - await fs.rename(tempPath, filePath); - } catch (error) { - try { - await fs.unlink(tempPath); - } catch {} - throw error; - } - // Restore original permission bits since the atomic rename replaces the - // inode and the temp file has default (0644) permissions. Mask off the - // file-type bits; POSIX leaves them unspecified for chmod. A chmod - // failure must not fail the write, which has already succeeded. - try { - await fs.chmod(filePath, origStats.mode & 0o777); - } catch {} - } - - return formattedDiff; -} - -// Memory-efficient implementation to get the last N lines of a file -export async function tailFile(filePath: string, numLines: number): Promise { - const CHUNK_SIZE = 1024; // Read 1KB at a time - const stats = await fs.stat(filePath); - const fileSize = stats.size; - - if (fileSize === 0) return ''; - - // Open file for reading - const fileHandle = await fs.open(filePath, 'r'); - try { - const chunks: Buffer[] = []; - let position = fileSize; - const chunk = Buffer.alloc(CHUNK_SIZE); - let newlinesFound = 0; - - // Read chunks from the end of the file until we have enough lines - while (position > 0 && newlinesFound < numLines) { - const size = Math.min(CHUNK_SIZE, position); - position -= size; - - const { bytesRead } = await fileHandle.read(chunk, 0, size, position); - if (!bytesRead) break; - - const readData = Buffer.from(chunk.subarray(0, bytesRead)); - chunks.unshift(readData); - for (const byte of readData) { - if (byte === 0x0a) newlinesFound++; - } - } - - const text = normalizeLineEndings(Buffer.concat(chunks).toString('utf-8')); - return text.split('\n').slice(-numLines).join('\n'); - } finally { - await fileHandle.close(); - } -} - -// New function to get the first N lines of a file -export async function headFile(filePath: string, numLines: number): Promise { - const fileHandle = await fs.open(filePath, 'r'); - try { - const lines: string[] = []; - let buffer = ''; - let bytesRead = 0; - const chunk = Buffer.alloc(1024); // 1KB buffer - const decoder = new StringDecoder('utf-8'); - - // Read chunks and count lines until we have enough or reach EOF - while (lines.length < numLines) { - const result = await fileHandle.read(chunk, 0, chunk.length, bytesRead); - if (result.bytesRead === 0) break; // End of file - bytesRead += result.bytesRead; - buffer += decoder.write(chunk.subarray(0, result.bytesRead)); - - const newLineIndex = buffer.lastIndexOf('\n'); - if (newLineIndex !== -1) { - const completeLines = buffer.slice(0, newLineIndex).split('\n'); - buffer = buffer.slice(newLineIndex + 1); - for (const line of completeLines) { - lines.push(line); - if (lines.length >= numLines) break; - } - } - } - - buffer += decoder.end(); - - // If there is leftover content and we still need lines, add it - if (buffer.length > 0 && lines.length < numLines) { - lines.push(buffer); - } - - return lines.join('\n'); - } finally { - await fileHandle.close(); - } -} - -export async function searchFilesWithValidation( - rootPath: string, - pattern: string, - allowedDirectories: string[], - options: SearchOptions = {} -): Promise { - const { excludePatterns = [] } = options; - const results: string[] = []; - - async function search(currentPath: string) { - const entries = await fs.readdir(currentPath, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(currentPath, entry.name); - - try { - await validatePath(fullPath); - - const relativePath = path.relative(rootPath, fullPath); - const shouldExclude = excludePatterns.some(excludePattern => - minimatch(relativePath, excludePattern, { dot: true }) - ); - - if (shouldExclude) continue; - - // Use glob matching for the search pattern - if (minimatch(relativePath, pattern, { dot: true })) { - results.push(fullPath); - } - - if (entry.isDirectory()) { - await search(fullPath); - } - } catch { - continue; - } - } - } - - await search(rootPath); - return results; -} diff --git a/src/filesystem/package.json b/src/filesystem/package.json index 3c288a2..581ad81 100644 --- a/src/filesystem/package.json +++ b/src/filesystem/package.json @@ -1,16 +1,11 @@ { "name": "@modelcontextprotocol/server-filesystem", - "version": "0.6.3", + "version": "0.5.1", "description": "MCP server for filesystem access", - "license": "SEE LICENSE IN LICENSE", - "mcpName": "io.github.modelcontextprotocol/server-filesystem", - "author": "Model Context Protocol a Series of LF Projects, LLC.", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", "homepage": "https://modelcontextprotocol.io", "bugs": "https://github.com/modelcontextprotocol/servers/issues", - "repository": { - "type": "git", - "url": "https://github.com/modelcontextprotocol/servers.git" - }, "type": "module", "bin": { "mcp-server-filesystem": "dist/index.js" @@ -21,23 +16,16 @@ "scripts": { "build": "tsc && shx chmod +x dist/*.js", "prepare": "npm run build", - "watch": "tsc --watch", - "test": "vitest run --coverage" + "watch": "tsc --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "diff": "^8.0.3", - "glob": "^13.0.6", - "minimatch": "^10.0.1", - "zod": "^4.0.0" + "@modelcontextprotocol/sdk": "0.5.0", + "glob": "^10.3.10", + "zod-to-json-schema": "^3.23.5" }, "devDependencies": { - "@types/diff": "^5.0.9", - "@types/minimatch": "^5.1.2", - "@types/node": "^22", - "@vitest/coverage-v8": "^4.1.8", - "shx": "^0.4.0", - "typescript": "^5.8.2", - "vitest": "^4.1.8" + "@types/node": "^20.11.0", + "shx": "^0.3.4", + "typescript": "^5.3.3" } } diff --git a/src/filesystem/path-utils.ts b/src/filesystem/path-utils.ts deleted file mode 100644 index 6ab5a59..0000000 --- a/src/filesystem/path-utils.ts +++ /dev/null @@ -1,125 +0,0 @@ -import path from "path"; -import os from 'os'; - -/** - * Converts WSL or Unix-style Windows paths to Windows format - * @param p The path to convert - * @returns Converted Windows path - */ -export function convertToWindowsPath(p: string): string { - // Handle WSL paths (/mnt/c/...) - // NEVER convert WSL paths - they are valid Linux paths that work with Node.js fs operations in WSL - // Converting them to Windows format (C:\...) breaks fs operations inside WSL - if (p.startsWith('/mnt/')) { - return p; // Leave WSL paths unchanged - } - - // Handle Unix-style Windows paths (/c/...) - // Only convert when running on Windows - if (p.match(/^\/[a-zA-Z]\//) && process.platform === 'win32') { - const driveLetter = p.charAt(1).toUpperCase(); - const pathPart = p.slice(2).replace(/\//g, '\\'); - return `${driveLetter}:${pathPart}`; - } - - // Handle standard Windows paths, ensuring backslashes - if (p.match(/^[a-zA-Z]:/)) { - return p.replace(/\//g, '\\'); - } - - // Leave non-Windows paths unchanged - return p; -} - -/** - * Normalizes path by standardizing format while preserving OS-specific behavior - * @param p The path to normalize - * @returns Normalized path - */ -export function normalizePath(p: string): string { - // Remove any surrounding quotes and whitespace - p = p.trim().replace(/^["']|["']$/g, ''); - - // Check if this is a Unix path that should not be converted - // WSL paths (/mnt/) should ALWAYS be preserved as they work correctly in WSL with Node.js fs - // Regular Unix paths should also be preserved - const isUnixPath = p.startsWith('/') && ( - // Always preserve WSL paths (/mnt/c/, /mnt/d/, etc.) - p.match(/^\/mnt\/[a-z]\//i) || - // On non-Windows platforms, treat all absolute paths as Unix paths - (process.platform !== 'win32') || - // On Windows, preserve Unix paths that aren't Unix-style Windows paths (/c/, /d/, etc.) - (process.platform === 'win32' && !p.match(/^\/[a-zA-Z]\//)) - ); - - if (isUnixPath) { - // For Unix paths, just normalize without converting to Windows format - // Replace double slashes with single slashes and remove trailing slashes - return p.replace(/\/+/g, '/').replace(/(? { - if (typeof dir !== 'string' || !dir) { - return false; - } - - // Reject null bytes in allowed dirs - if (dir.includes('\x00')) { - return false; - } - - // Normalize the allowed directory - let normalizedDir: string; - try { - normalizedDir = path.resolve(path.normalize(dir)); - } catch { - return false; - } - - // Verify allowed directory is absolute after normalization - if (!path.isAbsolute(normalizedDir)) { - throw new Error('Allowed directories must be absolute paths after normalization'); - } - - // Check if normalizedPath is within normalizedDir - // Path is inside if it's the same or a subdirectory - if (normalizedPath === normalizedDir) { - return true; - } - - // Special case for root directory to avoid double slash - // On Windows, we need to check if both paths are on the same drive - if (normalizedDir === path.sep) { - return normalizedPath.startsWith(path.sep); - } - - // On Windows, also check for drive root (e.g., "C:\") - if (path.sep === '\\' && normalizedDir.match(/^[A-Za-z]:\\?$/)) { - // Ensure both paths are on the same drive - const dirDrive = normalizedDir.charAt(0).toLowerCase(); - const pathDrive = normalizedPath.charAt(0).toLowerCase(); - return pathDrive === dirDrive && normalizedPath.startsWith(normalizedDir.replace(/\\?$/, '\\')); - } - - return normalizedPath.startsWith(normalizedDir + path.sep); - }); -} diff --git a/src/filesystem/roots-utils.ts b/src/filesystem/roots-utils.ts deleted file mode 100644 index 5e26bb2..0000000 --- a/src/filesystem/roots-utils.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { promises as fs, type Stats } from 'fs'; -import path from 'path'; -import os from 'os'; -import { normalizePath } from './path-utils.js'; -import type { Root } from '@modelcontextprotocol/sdk/types.js'; -import { fileURLToPath } from "url"; - -/** - * Converts a root URI to a normalized directory path with basic security validation. - * @param rootUri - File URI (file://...) or plain directory path - * @returns Promise resolving to validated path or null if invalid - */ -async function parseRootUri(rootUri: string): Promise { - try { - const rawPath = rootUri.startsWith('file://') ? fileURLToPath(rootUri) : rootUri; - const expandedPath = rawPath.startsWith('~/') || rawPath === '~' - ? path.join(os.homedir(), rawPath.slice(1)) - : rawPath; - const absolutePath = path.resolve(expandedPath); - const resolvedPath = await fs.realpath(absolutePath); - return normalizePath(resolvedPath); - } catch { - return null; // Path doesn't exist or other error - } -} - -/** - * Formats error message for directory validation failures. - * @param dir - Directory path that failed validation - * @param error - Error that occurred during validation - * @param reason - Specific reason for failure - * @returns Formatted error message - */ -function formatDirectoryError(dir: string, error?: unknown, reason?: string): string { - if (reason) { - return `Skipping ${reason}: ${dir}`; - } - const message = error instanceof Error ? error.message : String(error); - return `Skipping invalid directory: ${dir} due to error: ${message}`; -} - -/** - * Resolves requested root directories from MCP root specifications. - * - * Converts root URI specifications (file:// URIs or plain paths) into normalized - * directory paths, validating that each path exists and is a directory. - * Includes symlink resolution for security. - * - * @param requestedRoots - Array of root specifications with URI and optional name - * @returns Promise resolving to array of validated directory paths - */ -export async function getValidRootDirectories( - requestedRoots: readonly Root[] -): Promise { - const validatedDirectories: string[] = []; - - for (const requestedRoot of requestedRoots) { - const resolvedPath = await parseRootUri(requestedRoot.uri); - if (!resolvedPath) { - console.error(formatDirectoryError(requestedRoot.uri, undefined, 'invalid path or inaccessible')); - continue; - } - - try { - const stats: Stats = await fs.stat(resolvedPath); - if (stats.isDirectory()) { - validatedDirectories.push(resolvedPath); - } else { - console.error(formatDirectoryError(resolvedPath, undefined, 'non-directory root')); - } - } catch (error) { - console.error(formatDirectoryError(resolvedPath, error)); - } - } - - return validatedDirectories; -} \ No newline at end of file diff --git a/src/filesystem/tsconfig.json b/src/filesystem/tsconfig.json index db219c5..c0c20f3 100644 --- a/src/filesystem/tsconfig.json +++ b/src/filesystem/tsconfig.json @@ -8,11 +8,5 @@ }, "include": [ "./**/*.ts" - ], - "exclude": [ - "**/__tests__/**", - "**/*.test.ts", - "**/*.spec.ts", - "vitest.config.ts" ] } diff --git a/src/filesystem/vitest.config.ts b/src/filesystem/vitest.config.ts deleted file mode 100644 index d414ec8..0000000 --- a/src/filesystem/vitest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - include: ['**/__tests__/**/*.test.ts'], - coverage: { - provider: 'v8', - include: ['**/*.ts'], - exclude: ['**/__tests__/**', '**/dist/**'], - }, - }, -}); diff --git a/src/gdrive/README.md b/src/gdrive/README.md new file mode 100644 index 0000000..9a795f0 --- /dev/null +++ b/src/gdrive/README.md @@ -0,0 +1,68 @@ +# Google Drive server + +This MCP server integrates with Google Drive to allow listing, reading, and searching over files. + +## Components + +### Tools + +- **search** + - Search for files in Google Drive + - Input: `query` (string): Search query + - Returns file names and MIME types of matching files + +### Resources + +The server provides access to Google Drive files: + +- **Files** (`gdrive:///`) + - Supports all file types + - Google Workspace files are automatically exported: + - Docs โ†’ Markdown + - Sheets โ†’ CSV + - Presentations โ†’ Plain text + - Drawings โ†’ PNG + - Other files are provided in their native format + +## Getting started + +1. [Create a new Google Cloud project](https://console.cloud.google.com/projectcreate) +2. [Enable the Google Drive API](https://console.cloud.google.com/workspace-api/products) +3. [Configure an OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) ("internal" is fine for testing) +4. Add OAuth scope `https://www.googleapis.com/auth/drive.readonly` +5. [Create an OAuth Client ID](https://console.cloud.google.com/apis/credentials/oauthclient) for application type "Desktop App" +6. Download the JSON file of your client's OAuth keys +7. Rename the key file to `gcp-oauth.keys.json` and place into the root of this repo (i.e. `servers/gcp-oauth.keys.json`) + +Make sure to build the server with either `npm run build` or `npm run watch`. + +### Authentication + +To authenticate and save credentials: + +1. Run the server with the `auth` argument: `node ./dist auth` +2. This will open an authentication flow in your system browser +3. Complete the authentication process +4. Credentials will be saved in the root of this repo (i.e. `servers/.gdrive-server-credentials.json`) + +### Usage with Desktop App + +To integrate this server with the desktop app, add the following to your app's server configuration: + +```json +{ + "mcpServers": { + "gdrive": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-gdrive" + ] + } + } +} +``` + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/gdrive/index.ts b/src/gdrive/index.ts new file mode 100644 index 0000000..b9c6d91 --- /dev/null +++ b/src/gdrive/index.ts @@ -0,0 +1,218 @@ +#!/usr/bin/env node + +import { authenticate } from "@google-cloud/local-auth"; +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import fs from "fs"; +import { google } from "googleapis"; +import path from "path"; + +const drive = google.drive("v3"); + +const server = new Server( + { + name: "example-servers/gdrive", + version: "0.1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + }, +); + +server.setRequestHandler(ListResourcesRequestSchema, async (request) => { + const pageSize = 10; + const params: any = { + pageSize, + fields: "nextPageToken, files(id, name, mimeType)", + }; + + if (request.params?.cursor) { + params.pageToken = request.params.cursor; + } + + const res = await drive.files.list(params); + const files = res.data.files!; + + return { + resources: files.map((file) => ({ + uri: `gdrive:///${file.id}`, + mimeType: file.mimeType, + name: file.name, + })), + nextCursor: res.data.nextPageToken, + }; +}); + +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const fileId = request.params.uri.replace("gdrive:///", ""); + + // First get file metadata to check mime type + const file = await drive.files.get({ + fileId, + fields: "mimeType", + }); + + // For Google Docs/Sheets/etc we need to export + if (file.data.mimeType?.startsWith("application/vnd.google-apps")) { + let exportMimeType: string; + switch (file.data.mimeType) { + case "application/vnd.google-apps.document": + exportMimeType = "text/markdown"; + break; + case "application/vnd.google-apps.spreadsheet": + exportMimeType = "text/csv"; + break; + case "application/vnd.google-apps.presentation": + exportMimeType = "text/plain"; + break; + case "application/vnd.google-apps.drawing": + exportMimeType = "image/png"; + break; + default: + exportMimeType = "text/plain"; + } + + const res = await drive.files.export( + { fileId, mimeType: exportMimeType }, + { responseType: "text" }, + ); + + return { + contents: [ + { + uri: request.params.uri, + mimeType: exportMimeType, + text: res.data, + }, + ], + }; + } + + // For regular files download content + const res = await drive.files.get( + { fileId, alt: "media" }, + { responseType: "arraybuffer" }, + ); + const mimeType = file.data.mimeType || "application/octet-stream"; + if (mimeType.startsWith("text/") || mimeType === "application/json") { + return { + contents: [ + { + uri: request.params.uri, + mimeType: mimeType, + text: Buffer.from(res.data as ArrayBuffer).toString("utf-8"), + }, + ], + }; + } else { + return { + contents: [ + { + uri: request.params.uri, + mimeType: mimeType, + blob: Buffer.from(res.data as ArrayBuffer).toString("base64"), + }, + ], + }; + } +}); + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "search", + description: "Search for files in Google Drive", + inputSchema: { + type: "object", + properties: { + query: { + type: "string", + description: "Search query", + }, + }, + required: ["query"], + }, + }, + ], + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name === "search") { + const userQuery = request.params.arguments?.query as string; + const escapedQuery = userQuery.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); + const formattedQuery = `fullText contains '${escapedQuery}'`; + + const res = await drive.files.list({ + q: formattedQuery, + pageSize: 10, + fields: "files(id, name, mimeType, modifiedTime, size)", + }); + + const fileList = res.data.files + ?.map((file: any) => `${file.name} (${file.mimeType})`) + .join("\n"); + return { + content: [ + { + type: "text", + text: `Found ${res.data.files?.length ?? 0} files:\n${fileList}`, + }, + ], + isError: false, + }; + } + throw new Error("Tool not found"); +}); + +const credentialsPath = path.join( + path.dirname(new URL(import.meta.url).pathname), + "../../../.gdrive-server-credentials.json", +); + +async function authenticateAndSaveCredentials() { + console.log("Launching auth flowโ€ฆ"); + const auth = await authenticate({ + keyfilePath: path.join( + path.dirname(new URL(import.meta.url).pathname), + "../../../gcp-oauth.keys.json", + ), + scopes: ["https://www.googleapis.com/auth/drive.readonly"], + }); + fs.writeFileSync(credentialsPath, JSON.stringify(auth.credentials)); + console.log("Credentials saved. You can now run the server."); +} + +async function loadCredentialsAndRunServer() { + if (!fs.existsSync(credentialsPath)) { + console.error( + "Credentials not found. Please run with 'auth' argument first.", + ); + process.exit(1); + } + + const credentials = JSON.parse(fs.readFileSync(credentialsPath, "utf-8")); + const auth = new google.auth.OAuth2(); + auth.setCredentials(credentials); + google.options({ auth }); + + console.log("Credentials loaded. Starting server."); + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +if (process.argv[2] === "auth") { + authenticateAndSaveCredentials().catch(console.error); +} else { + loadCredentialsAndRunServer().catch(console.error); +} diff --git a/src/gdrive/package.json b/src/gdrive/package.json new file mode 100644 index 0000000..5b16eda --- /dev/null +++ b/src/gdrive/package.json @@ -0,0 +1,31 @@ +{ + "name": "@modelcontextprotocol/server-gdrive", + "version": "0.5.1", + "description": "MCP server for interacting with Google Drive", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-gdrive": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@google-cloud/local-auth": "^3.0.1", + "@modelcontextprotocol/sdk": "0.5.0", + "googleapis": "^144.0.0" + }, + "devDependencies": { + "@types/node": "^22.9.3", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/sequentialthinking/tsconfig.json b/src/gdrive/tsconfig.json similarity index 69% rename from src/sequentialthinking/tsconfig.json rename to src/gdrive/tsconfig.json index d2d8655..ec5da15 100644 --- a/src/sequentialthinking/tsconfig.json +++ b/src/gdrive/tsconfig.json @@ -6,9 +6,5 @@ }, "include": [ "./**/*.ts" - ], - "exclude": [ - "**/*.test.ts", - "vitest.config.ts" ] } diff --git a/src/git/Dockerfile b/src/git/Dockerfile deleted file mode 100644 index 4af4161..0000000 --- a/src/git/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -# Use a Python image with uv pre-installed -FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv - -# Install the project into `/app` -WORKDIR /app - -# Enable bytecode compilation -ENV UV_COMPILE_BYTECODE=1 - -# Copy from the cache instead of linking since it's a mounted volume -ENV UV_LINK_MODE=copy - -# Install the project's dependencies using the lockfile and settings -COPY uv.lock pyproject.toml ./ -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev --no-editable - -# Then, add the rest of the project source code and install it -# Installing separately from its dependencies allows optimal layer caching -ADD . /app -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev --no-editable - -FROM python:3.12-slim-bookworm - -RUN apt-get update && apt-get install -y git git-lfs && rm -rf /var/lib/apt/lists/* \ - && git lfs install --system - -WORKDIR /app - -COPY --from=uv /root/.local /root/.local - -RUN if ! id -u app >/dev/null 2>&1; then \ - useradd -rUM -s /usr/sbin/nologin app; \ - fi -COPY --from=uv --chown=app:app /app/.venv /app/.venv - -# Place executables in the environment at the front of the path -ENV PATH="/app/.venv/bin:$PATH" - -# when running the container, add --db-path and a bind mount to the host's db file -ENTRYPOINT ["mcp-server-git"] diff --git a/src/git/README.md b/src/git/README.md index 70b0097..c94ca7e 100644 --- a/src/git/README.md +++ b/src/git/README.md @@ -1,15 +1,9 @@ # mcp-server-git: A git MCP server - - ## Overview A Model Context Protocol server for Git repository interaction and automation. This server provides tools to read, search, and manipulate Git repositories via Large Language Models. -Source: https://github.com/modelcontextprotocol/servers/tree/main/src/git - -Requires MCP Python SDK 1.x (`mcp>=1.29.0,<2`). SDK 2.0 renamed APIs this server uses. The port to v2 is in progress. - Please note that mcp-server-git is currently in early development. The functionality and available tools are subject to change and expansion as we continue to develop and improve the server. ### Tools @@ -22,83 +16,43 @@ Please note that mcp-server-git is currently in early development. The functiona 2. `git_diff_unstaged` - Shows changes in working directory not yet staged - - Inputs: + - Input: - `repo_path` (string): Path to Git repository - - `context_lines` (number, optional): Number of context lines to show (default: 3) - Returns: Diff output of unstaged changes 3. `git_diff_staged` - Shows changes that are staged for commit - - Inputs: + - Input: - `repo_path` (string): Path to Git repository - - `context_lines` (number, optional): Number of context lines to show (default: 3) - Returns: Diff output of staged changes -4. `git_diff` - - Shows differences between branches or commits - - Inputs: - - `repo_path` (string): Path to Git repository - - `target` (string): Target branch or commit to compare with - - `context_lines` (number, optional): Number of context lines to show (default: 3) - - Returns: Diff output comparing current state with target - -5. `git_commit` +4. `git_commit` - Records changes to the repository - Inputs: - `repo_path` (string): Path to Git repository - `message` (string): Commit message - Returns: Confirmation with new commit hash -6. `git_add` +5. `git_add` - Adds file contents to the staging area - Inputs: - `repo_path` (string): Path to Git repository - `files` (string[]): Array of file paths to stage - Returns: Confirmation of staged files -7. `git_reset` +6. `git_reset` - Unstages all staged changes - Input: - `repo_path` (string): Path to Git repository - Returns: Confirmation of reset operation -8. `git_log` - - Shows the commit logs with optional date filtering +7. `git_log` + - Shows the commit logs - Inputs: - `repo_path` (string): Path to Git repository - `max_count` (number, optional): Maximum number of commits to show (default: 10) - - `start_timestamp` (string, optional): Start timestamp for filtering commits. Accepts ISO 8601 format (e.g., '2024-01-15T14:30:25'), relative dates (e.g., '2 weeks ago', 'yesterday'), or absolute dates (e.g., '2024-01-15', 'Jan 15 2024') - - `end_timestamp` (string, optional): End timestamp for filtering commits. Accepts ISO 8601 format (e.g., '2024-01-15T14:30:25'), relative dates (e.g., '2 weeks ago', 'yesterday'), or absolute dates (e.g., '2024-01-15', 'Jan 15 2024') - Returns: Array of commit entries with hash, author, date, and message -9. `git_create_branch` - - Creates a new branch - - Inputs: - - `repo_path` (string): Path to Git repository - - `branch_name` (string): Name of the new branch - - `base_branch` (string, optional): Base branch to create from (defaults to current branch) - - Returns: Confirmation of branch creation -10. `git_checkout` - - Switches branches - - Inputs: - - `repo_path` (string): Path to Git repository - - `branch_name` (string): Name of branch to checkout - - Returns: Confirmation of branch switch -11. `git_show` - - Shows the contents of a commit - - Inputs: - - `repo_path` (string): Path to Git repository - - `revision` (string): The revision (commit hash, branch name, tag) to show - - Returns: Contents of the specified commit - -12. `git_branch` - - List Git branches - - Inputs: - - `repo_path` (string): Path to the Git repository. - - `branch_type` (string): Whether to list local branches ('local'), remote branches ('remote') or all branches('all'). - - `contains` (string, optional): The commit sha that branch should contain. Do not pass anything to this param if no commit sha is specified - - `not_contains` (string, optional): The commit sha that branch should NOT contain. Do not pass anything to this param if no commit sha is specified - - Returns: List of branches ## Installation @@ -140,21 +94,6 @@ Add this to your `claude_desktop_config.json`: ```
-
-Using docker - -* Note: replace '/Users/username' with a path that you want to be accessible by this tool - -```json -"mcpServers": { - "git": { - "command": "docker", - "args": ["run", "--rm", "-i", "--mount", "type=bind,src=/Users/username,dst=/Users/username", "mcp/git"] - } -} -``` -
-
Using pip installation @@ -168,56 +107,6 @@ Add this to your `claude_desktop_config.json`: ```
-### Usage with VS Code - -For quick installation, use one of the one-click install buttons below... - -[![Install with UV in VS Code](https://img.shields.io/badge/VS_Code-UV-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=git&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-git%22%5D%7D) [![Install with UV in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-UV-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=git&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-git%22%5D%7D&quality=insiders) - -[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=git&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22--mount%22%2C%22type%3Dbind%2Csrc%3D%24%7BworkspaceFolder%7D%2Cdst%3D%2Fworkspace%22%2C%22mcp%2Fgit%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=git&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22--mount%22%2C%22type%3Dbind%2Csrc%3D%24%7BworkspaceFolder%7D%2Cdst%3D%2Fworkspace%22%2C%22mcp%2Fgit%22%5D%7D&quality=insiders) - -For manual installation, you can configure the MCP server using one of these methods: - -**Method 1: User Configuration (Recommended)** -Add the configuration to your user-level MCP configuration file. Open the Command Palette (`Ctrl + Shift + P`) and run `MCP: Open User Configuration`. This will open your user `mcp.json` file where you can add the server configuration. - -**Method 2: Workspace Configuration** -Alternatively, you can add the configuration to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. - -> For more details about MCP configuration in VS Code, see the [official VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers). - -```json -{ - "servers": { - "git": { - "command": "uvx", - "args": ["mcp-server-git"] - } - } -} -``` - -For Docker installation: - -```json -{ - "mcp": { - "servers": { - "git": { - "command": "docker", - "args": [ - "run", - "--rm", - "-i", - "--mount", "type=bind,src=${workspaceFolder},dst=/workspace", - "mcp/git" - ] - } - } - } -} -``` - ### Usage with [Zed](https://github.com/zed-industries/zed) Add to your Zed settings.json: @@ -252,24 +141,6 @@ Add to your Zed settings.json: ``` -### Usage with [Zencoder](https://zencoder.ai) - -1. Go to the Zencoder menu (...) -2. From the dropdown menu, select `Agent Tools` -3. Click on the `Add Custom MCP` -4. Add the name (i.e. git) and server configuration from below, and make sure to hit the `Install` button - -
-Using uvx - -```json -{ - "command": "uvx", - "args": ["mcp-server-git", "--repository", "path/to/git/repo"] -} -``` -
- ## Debugging You can use the MCP inspector to debug the server. For uvx installations: @@ -285,64 +156,6 @@ cd path/to/servers/src/git npx @modelcontextprotocol/inspector uv run mcp-server-git ``` -Running `tail -n 20 -f ~/Library/Logs/Claude/mcp*.log` will show the logs from the server and may -help you debug any issues. - -## Development - -If you are doing local development, there are two ways to test your changes: - -1. Run the MCP inspector to test your changes. See [Debugging](#debugging) for run instructions. - -2. Test using the Claude desktop app. Add the following to your `claude_desktop_config.json`: - -### Docker - -```json -{ - "mcpServers": { - "git": { - "command": "docker", - "args": [ - "run", - "--rm", - "-i", - "--mount", "type=bind,src=/Users/username/Desktop,dst=/projects/Desktop", - "--mount", "type=bind,src=/path/to/other/allowed/dir,dst=/projects/other/allowed/dir,ro", - "--mount", "type=bind,src=/path/to/file.txt,dst=/projects/path/to/file.txt", - "mcp/git" - ] - } - } -} -``` - -### UVX -```json -{ -"mcpServers": { - "git": { - "command": "uv", - "args": [ - "--directory", - "//mcp-servers/src/git", - "run", - "mcp-server-git" - ] - } - } -} -``` - -## Build - -Docker build: - -```bash -cd src/git -docker build -t mcp/git . -``` - ## License This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/git/pyproject.toml b/src/git/pyproject.toml index e2407ba..1356858 100644 --- a/src/git/pyproject.toml +++ b/src/git/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-server-git" -version = "0.6.2" +version = "0.6.1" description = "A Model Context Protocol server providing tools to read, search, and manipulate Git repositories programmatically via LLMs" readme = "README.md" requires-python = ">=3.10" @@ -17,15 +17,11 @@ classifiers = [ ] dependencies = [ "click>=8.1.7", - "gitpython>=3.1.50", - "mcp>=1.29.0,<2", + "gitpython>=3.1.43", + "mcp>=1.0.0", "pydantic>=2.0.0", ] -[project.urls] -Repository = "https://github.com/modelcontextprotocol/servers/tree/main/src/git" -Issues = "https://github.com/modelcontextprotocol/servers/issues" - [project.scripts] mcp-server-git = "mcp_server_git:main" @@ -33,11 +29,5 @@ mcp-server-git = "mcp_server_git:main" requires = ["hatchling"] build-backend = "hatchling.build" -[dependency-groups] -dev = ["pyright>=1.1.407", "ruff>=0.7.3", "pytest>=8.0.0"] - -[tool.pytest.ini_options] -testpaths = ["tests"] -python_files = "test_*.py" -python_classes = "Test*" -python_functions = "test_*" +[tool.uv] +dev-dependencies = ["pyright>=1.1.389", "ruff>=0.7.3"] diff --git a/src/git/src/mcp_server_git/py.typed b/src/git/src/mcp_server_git/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/src/git/src/mcp_server_git/server.py b/src/git/src/mcp_server_git/server.py index b94af84..fe1e3f5 100644 --- a/src/git/src/mcp_server_git/server.py +++ b/src/git/src/mcp_server_git/server.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import Any, Optional, Sequence +from typing import Sequence from mcp.server import Server from mcp.server.session import ServerSession from mcp.server.stdio import stdio_server @@ -10,31 +10,19 @@ from mcp.types import ( Tool, ListRootsResult, RootsCapability, - ToolAnnotations, ) from enum import Enum import git -from git.exc import BadName -from pydantic import BaseModel, Field - -# Default number of context lines to show in diff output -DEFAULT_CONTEXT_LINES = 3 +from pydantic import BaseModel class GitStatus(BaseModel): repo_path: str class GitDiffUnstaged(BaseModel): repo_path: str - context_lines: int = DEFAULT_CONTEXT_LINES class GitDiffStaged(BaseModel): repo_path: str - context_lines: int = DEFAULT_CONTEXT_LINES - -class GitDiff(BaseModel): - repo_path: str - target: str - context_lines: int = DEFAULT_CONTEXT_LINES class GitCommit(BaseModel): repo_path: str @@ -50,126 +38,39 @@ class GitReset(BaseModel): class GitLog(BaseModel): repo_path: str max_count: int = 10 - start_timestamp: Optional[str] = Field( - None, - description="Start timestamp for filtering commits. Accepts: ISO 8601 format (e.g., '2024-01-15T14:30:25'), relative dates (e.g., '2 weeks ago', 'yesterday'), or absolute dates (e.g., '2024-01-15', 'Jan 15 2024')" - ) - end_timestamp: Optional[str] = Field( - None, - description="End timestamp for filtering commits. Accepts: ISO 8601 format (e.g., '2024-01-15T14:30:25'), relative dates (e.g., '2 weeks ago', 'yesterday'), or absolute dates (e.g., '2024-01-15', 'Jan 15 2024')" - ) - -class GitCreateBranch(BaseModel): - repo_path: str - branch_name: str - base_branch: str | None = None - -class GitCheckout(BaseModel): - repo_path: str - branch_name: str - -class GitShow(BaseModel): - repo_path: str - revision: str - - - -class GitBranch(BaseModel): - repo_path: str = Field( - ..., - description="The path to the Git repository.", - ) - branch_type: str = Field( - ..., - description="Whether to list local branches ('local'), remote branches ('remote') or all branches('all').", - ) - contains: Optional[str] = Field( - None, - description="The commit sha that branch should contain. Do not pass anything to this param if no commit sha is specified", - ) - not_contains: Optional[str] = Field( - None, - description="The commit sha that branch should NOT contain. Do not pass anything to this param if no commit sha is specified", - ) - class GitTools(str, Enum): STATUS = "git_status" DIFF_UNSTAGED = "git_diff_unstaged" DIFF_STAGED = "git_diff_staged" - DIFF = "git_diff" COMMIT = "git_commit" ADD = "git_add" RESET = "git_reset" LOG = "git_log" - CREATE_BRANCH = "git_create_branch" - CHECKOUT = "git_checkout" - SHOW = "git_show" - - BRANCH = "git_branch" def git_status(repo: git.Repo) -> str: return repo.git.status() -def git_diff_unstaged(repo: git.Repo, context_lines: int = DEFAULT_CONTEXT_LINES) -> str: - return repo.git.diff(f"--unified={context_lines}") +def git_diff_unstaged(repo: git.Repo) -> str: + return repo.git.diff() -def git_diff_staged(repo: git.Repo, context_lines: int = DEFAULT_CONTEXT_LINES) -> str: - return repo.git.diff(f"--unified={context_lines}", "--cached") - -def git_diff(repo: git.Repo, target: str, context_lines: int = DEFAULT_CONTEXT_LINES) -> str: - # Defense in depth: reject targets starting with '-' to prevent flag injection, - # even if a malicious ref with that name exists (e.g. via filesystem manipulation) - if target.startswith("-"): - raise BadName(f"Invalid target: '{target}' - cannot start with '-'") - repo.rev_parse(target) # Validates target is a real git ref, throws BadName if not - return repo.git.diff(f"--unified={context_lines}", target) +def git_diff_staged(repo: git.Repo) -> str: + return repo.git.diff("--cached") def git_commit(repo: git.Repo, message: str) -> str: commit = repo.index.commit(message) return f"Changes committed successfully with hash {commit.hexsha}" def git_add(repo: git.Repo, files: list[str]) -> str: - if files == ["."]: - repo.git.add(".") - else: - # Defense in depth: validate each path resolves within the repository - # working tree to prevent path traversal (e.g. '../../etc/passwd' or an - # absolute path) from staging files outside repository boundaries. - repo_root = Path(repo.working_dir).resolve() - for f in files: - try: - resolved = (repo_root / f).resolve() - except (OSError, RuntimeError): - raise ValueError(f"Invalid path: '{f}'") - try: - resolved.relative_to(repo_root) - except ValueError: - raise ValueError( - f"Path '{f}' is outside the repository '{repo_root}'" - ) - # Use '--' to prevent files starting with '-' from being interpreted as options - repo.git.add("--", *files) + repo.index.add(files) return "Files staged successfully" def git_reset(repo: git.Repo) -> str: repo.index.reset() return "All staged changes reset" -def git_log(repo: git.Repo, max_count: int = 10, start_timestamp: Optional[str] = None, end_timestamp: Optional[str] = None) -> list[str]: - # Defense in depth: reject timestamps starting with '-' to prevent flag injection - if start_timestamp and start_timestamp.startswith("-"): - raise ValueError(f"Invalid start_timestamp: '{start_timestamp}' - cannot start with '-'") - if end_timestamp and end_timestamp.startswith("-"): - raise ValueError(f"Invalid end_timestamp: '{end_timestamp}' - cannot start with '-'") - - kwargs: dict[str, Any] = {"max_count": max_count} - if start_timestamp: - kwargs["since"] = start_timestamp - if end_timestamp: - kwargs["until"] = end_timestamp - - commits = list(repo.iter_commits(**kwargs)) +def git_log(repo: git.Repo, max_count: int = 10) -> list[str]: + commits = list(repo.iter_commits(max_count=max_count)) log = [] for commit in commits: log.append( @@ -180,114 +81,6 @@ def git_log(repo: git.Repo, max_count: int = 10, start_timestamp: Optional[str] ) return log -def git_create_branch(repo: git.Repo, branch_name: str, base_branch: str | None = None) -> str: - # Defense in depth: reject names starting with '-' to prevent flag injection - if branch_name.startswith("-"): - raise BadName(f"Invalid branch name: '{branch_name}' - cannot start with '-'") - if base_branch and base_branch.startswith("-"): - raise BadName(f"Invalid base branch: '{base_branch}' - cannot start with '-'") - if base_branch: - base = repo.references[base_branch] - else: - base = repo.active_branch - - repo.create_head(branch_name, base) - return f"Created branch '{branch_name}' from '{base.name}'" - -def git_checkout(repo: git.Repo, branch_name: str) -> str: - # Defense in depth: reject branch names starting with '-' to prevent flag injection, - # even if a malicious ref with that name exists (e.g. via filesystem manipulation) - if branch_name.startswith("-"): - raise BadName(f"Invalid branch name: '{branch_name}' - cannot start with '-'") - repo.rev_parse(branch_name) # Validates branch_name is a real git ref, throws BadName if not - repo.git.checkout(branch_name) - return f"Switched to branch '{branch_name}'" - - - -def git_show(repo: git.Repo, revision: str) -> str: - # Defense in depth: reject revisions starting with '-' to prevent flag injection, - # even if a malicious ref with that name exists (e.g. via filesystem manipulation) - if revision.startswith("-"): - raise BadName(f"Invalid revision: '{revision}' - cannot start with '-'") - commit = repo.commit(revision) - output = [ - f"Commit: {commit.hexsha!r}\n" - f"Author: {commit.author!r}\n" - f"Date: {commit.authored_datetime!r}\n" - f"Message: {commit.message!r}\n" - ] - if commit.parents: - parent = commit.parents[0] - diff = parent.diff(commit, create_patch=True) - else: - diff = commit.diff(git.NULL_TREE, create_patch=True) - for d in diff: - output.append(f"\n--- {d.a_path}\n+++ {d.b_path}\n") - if d.diff is None: - continue - if isinstance(d.diff, bytes): - output.append(d.diff.decode('utf-8')) - else: - output.append(d.diff) - return "".join(output) - -def validate_repo_path(repo_path: Path, allowed_repository: Path | None) -> None: - """Validate that repo_path is within the allowed repository path.""" - if allowed_repository is None: - return # No restriction configured - - # Resolve both paths to handle symlinks and relative paths - try: - resolved_repo = repo_path.resolve() - resolved_allowed = allowed_repository.resolve() - except (OSError, RuntimeError): - raise ValueError(f"Invalid path: {repo_path}") - - # Check if repo_path is the same as or a subdirectory of allowed_repository - try: - resolved_repo.relative_to(resolved_allowed) - except ValueError: - raise ValueError( - f"Repository path '{repo_path}' is outside the allowed repository '{allowed_repository}'" - ) - - -def git_branch(repo: git.Repo, branch_type: str, contains: str | None = None, not_contains: str | None = None) -> str: - # Defense in depth: reject values starting with '-' to prevent flag injection - if contains and contains.startswith("-"): - raise BadName(f"Invalid contains value: '{contains}' - cannot start with '-'") - if not_contains and not_contains.startswith("-"): - raise BadName(f"Invalid not_contains value: '{not_contains}' - cannot start with '-'") - - match contains: - case None: - contains_sha = (None,) - case _: - contains_sha = ("--contains", contains) - - match not_contains: - case None: - not_contains_sha = (None,) - case _: - not_contains_sha = ("--no-contains", not_contains) - - match branch_type: - case 'local': - b_type = None - case 'remote': - b_type = "-r" - case 'all': - b_type = "-a" - case _: - return f"Invalid branch type: {branch_type}" - - # None value will be auto deleted by GitPython - branch_info = repo.git.branch(b_type, *contains_sha, *not_contains_sha) - - return branch_info - - async def serve(repository: Path | None) -> None: logger = logging.getLogger(__name__) @@ -307,135 +100,38 @@ async def serve(repository: Path | None) -> None: Tool( name=GitTools.STATUS, description="Shows the working tree status", - inputSchema=GitStatus.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + inputSchema=GitStatus.schema(), ), Tool( name=GitTools.DIFF_UNSTAGED, description="Shows changes in the working directory that are not yet staged", - inputSchema=GitDiffUnstaged.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + inputSchema=GitDiffUnstaged.schema(), ), Tool( name=GitTools.DIFF_STAGED, description="Shows changes that are staged for commit", - inputSchema=GitDiffStaged.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), - ), - Tool( - name=GitTools.DIFF, - description="Shows differences between branches or commits", - inputSchema=GitDiff.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + inputSchema=GitDiffStaged.schema(), ), Tool( name=GitTools.COMMIT, description="Records changes to the repository", - inputSchema=GitCommit.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=False, - idempotentHint=False, - openWorldHint=False, - ), + inputSchema=GitCommit.schema(), ), Tool( name=GitTools.ADD, description="Adds file contents to the staging area", - inputSchema=GitAdd.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + inputSchema=GitAdd.schema(), ), Tool( name=GitTools.RESET, description="Unstages all staged changes", - inputSchema=GitReset.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=True, - idempotentHint=True, - openWorldHint=False, - ), + inputSchema=GitReset.schema(), ), Tool( name=GitTools.LOG, description="Shows the commit logs", - inputSchema=GitLog.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), + inputSchema=GitLog.schema(), ), - Tool( - name=GitTools.CREATE_BRANCH, - description="Creates a new branch from an optional base branch", - inputSchema=GitCreateBranch.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=False, - idempotentHint=False, - openWorldHint=False, - ), - ), - Tool( - name=GitTools.CHECKOUT, - description="Switches branches", - inputSchema=GitCheckout.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=False, - destructiveHint=False, - idempotentHint=False, - openWorldHint=False, - ), - ), - Tool( - name=GitTools.SHOW, - description="Shows the contents of a commit", - inputSchema=GitShow.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), - ), - Tool( - name=GitTools.BRANCH, - description="List Git branches", - inputSchema=GitBranch.model_json_schema(), - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), - ) ] async def list_repos() -> Sequence[str]: @@ -470,11 +166,6 @@ async def serve(repository: Path | None) -> None: @server.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: repo_path = Path(arguments["repo_path"]) - - # Validate repo_path is within allowed repository - validate_repo_path(repo_path, repository) - - # For all commands, we need an existing repo repo = git.Repo(repo_path) match name: @@ -486,26 +177,19 @@ async def serve(repository: Path | None) -> None: )] case GitTools.DIFF_UNSTAGED: - diff = git_diff_unstaged(repo, arguments.get("context_lines", DEFAULT_CONTEXT_LINES)) + diff = git_diff_unstaged(repo) return [TextContent( type="text", text=f"Unstaged changes:\n{diff}" )] case GitTools.DIFF_STAGED: - diff = git_diff_staged(repo, arguments.get("context_lines", DEFAULT_CONTEXT_LINES)) + diff = git_diff_staged(repo) return [TextContent( type="text", text=f"Staged changes:\n{diff}" )] - case GitTools.DIFF: - diff = git_diff(repo, arguments["target"], arguments.get("context_lines", DEFAULT_CONTEXT_LINES)) - return [TextContent( - type="text", - text=f"Diff with {arguments['target']}:\n{diff}" - )] - case GitTools.COMMIT: result = git_commit(repo, arguments["message"]) return [TextContent( @@ -527,59 +211,16 @@ async def serve(repository: Path | None) -> None: text=result )] - # Update the LOG case: case GitTools.LOG: - log = git_log( - repo, - arguments.get("max_count", 10), - arguments.get("start_timestamp"), - arguments.get("end_timestamp") - ) + log = git_log(repo, arguments.get("max_count", 10)) return [TextContent( type="text", text="Commit history:\n" + "\n".join(log) )] - case GitTools.CREATE_BRANCH: - result = git_create_branch( - repo, - arguments["branch_name"], - arguments.get("base_branch") - ) - return [TextContent( - type="text", - text=result - )] - - case GitTools.CHECKOUT: - result = git_checkout(repo, arguments["branch_name"]) - return [TextContent( - type="text", - text=result - )] - - case GitTools.SHOW: - result = git_show(repo, arguments["revision"]) - return [TextContent( - type="text", - text=result - )] - - case GitTools.BRANCH: - result = git_branch( - repo, - arguments.get("branch_type", 'local'), - arguments.get("contains", None), - arguments.get("not_contains", None), - ) - return [TextContent( - type="text", - text=result - )] - case _: raise ValueError(f"Unknown tool: {name}") options = server.create_initialization_options() async with stdio_server() as (read_stream, write_stream): - await server.run(read_stream, write_stream, options) + await server.run(read_stream, write_stream, options, raise_exceptions=True) diff --git a/src/git/tests/test_server.py b/src/git/tests/test_server.py deleted file mode 100644 index 05d5931..0000000 --- a/src/git/tests/test_server.py +++ /dev/null @@ -1,590 +0,0 @@ -import pytest -from pathlib import Path -import git -from git.exc import BadName -from mcp_server_git.server import ( - git_checkout, - git_branch, - git_add, - git_status, - git_diff_unstaged, - git_diff_staged, - git_diff, - git_commit, - git_reset, - git_log, - git_create_branch, - git_show, - validate_repo_path, - serve, -) -import shutil -import unittest.mock as mock - -@pytest.fixture -def test_repository(tmp_path: Path): - repo_path = tmp_path / "temp_test_repo" - test_repo = git.Repo.init(repo_path) - - Path(repo_path / "test.txt").write_text("test") - test_repo.index.add(["test.txt"]) - test_repo.index.commit("initial commit") - - yield test_repo - - shutil.rmtree(repo_path) - -def test_git_checkout_existing_branch(test_repository): - test_repository.git.branch("test-branch") - result = git_checkout(test_repository, "test-branch") - - assert "Switched to branch 'test-branch'" in result - assert test_repository.active_branch.name == "test-branch" - -def test_git_checkout_nonexistent_branch(test_repository): - - with pytest.raises(BadName): - git_checkout(test_repository, "nonexistent-branch") - -def test_git_branch_local(test_repository): - test_repository.git.branch("new-branch-local") - result = git_branch(test_repository, "local") - assert "new-branch-local" in result - -def test_git_branch_remote(test_repository): - result = git_branch(test_repository, "remote") - assert "" == result.strip() # Should be empty if no remote branches - -def test_git_branch_all(test_repository): - test_repository.git.branch("new-branch-all") - result = git_branch(test_repository, "all") - assert "new-branch-all" in result - -def test_git_branch_contains(test_repository): - # Get the default branch name (could be "main" or "master") - default_branch = test_repository.active_branch.name - # Create a new branch and commit to it - test_repository.git.checkout("-b", "feature-branch") - Path(test_repository.working_dir / Path("feature.txt")).write_text("feature content") - test_repository.index.add(["feature.txt"]) - commit = test_repository.index.commit("feature commit") - test_repository.git.checkout(default_branch) - - result = git_branch(test_repository, "local", contains=commit.hexsha) - assert "feature-branch" in result - assert default_branch not in result - -def test_git_branch_not_contains(test_repository): - # Get the default branch name (could be "main" or "master") - default_branch = test_repository.active_branch.name - # Create a new branch and commit to it - test_repository.git.checkout("-b", "another-feature-branch") - Path(test_repository.working_dir / Path("another_feature.txt")).write_text("another feature content") - test_repository.index.add(["another_feature.txt"]) - commit = test_repository.index.commit("another feature commit") - test_repository.git.checkout(default_branch) - - result = git_branch(test_repository, "local", not_contains=commit.hexsha) - assert "another-feature-branch" not in result - assert default_branch in result - -def test_git_add_all_files(test_repository): - file_path = Path(test_repository.working_dir) / "all_file.txt" - file_path.write_text("adding all") - - result = git_add(test_repository, ["."]) - - staged_files = [item.a_path for item in test_repository.index.diff("HEAD")] - assert "all_file.txt" in staged_files - assert result == "Files staged successfully" - -def test_git_add_specific_files(test_repository): - file1 = Path(test_repository.working_dir) / "file1.txt" - file2 = Path(test_repository.working_dir) / "file2.txt" - file1.write_text("file 1 content") - file2.write_text("file 2 content") - - result = git_add(test_repository, ["file1.txt"]) - - staged_files = [item.a_path for item in test_repository.index.diff("HEAD")] - assert "file1.txt" in staged_files - assert "file2.txt" not in staged_files - assert result == "Files staged successfully" - -def test_git_add_rejects_path_traversal(test_repository): - # Security invariant (CVE-2026-27735): a relative path escaping the - # repository must never be staged. Accept rejection from either the - # defense-in-depth validation (ValueError) or the underlying git CLI - # (GitCommandError) so the test asserts the property, not the layer. - outside = Path(test_repository.working_dir).parent / "outside.txt" - outside.write_text("secret") - - with pytest.raises((ValueError, git.GitCommandError)): - git_add(test_repository, ["../outside.txt"]) - - staged = [path for path, _stage in test_repository.index.entries] - assert "../outside.txt" not in staged - assert "outside.txt" not in staged - -def test_git_add_rejects_absolute_path_outside(test_repository): - # An absolute path outside the repository must never be staged. - outside = Path(test_repository.working_dir).parent / "abs_outside.txt" - outside.write_text("secret") - - with pytest.raises((ValueError, git.GitCommandError)): - git_add(test_repository, [str(outside)]) - - staged = [path for path, _stage in test_repository.index.entries] - assert "abs_outside.txt" not in staged - -def test_git_status(test_repository): - result = git_status(test_repository) - - assert result is not None - assert "On branch" in result or "branch" in result.lower() - -def test_git_diff_unstaged(test_repository): - file_path = Path(test_repository.working_dir) / "test.txt" - file_path.write_text("modified content") - - result = git_diff_unstaged(test_repository) - - assert "test.txt" in result - assert "modified content" in result - -def test_git_diff_unstaged_empty(test_repository): - result = git_diff_unstaged(test_repository) - - assert result == "" - -def test_git_diff_staged(test_repository): - file_path = Path(test_repository.working_dir) / "staged_file.txt" - file_path.write_text("staged content") - test_repository.index.add(["staged_file.txt"]) - - result = git_diff_staged(test_repository) - - assert "staged_file.txt" in result - assert "staged content" in result - -def test_git_diff_staged_empty(test_repository): - result = git_diff_staged(test_repository) - - assert result == "" - -def test_git_diff(test_repository): - # Get the default branch name (could be "main" or "master") - default_branch = test_repository.active_branch.name - test_repository.git.checkout("-b", "feature-diff") - file_path = Path(test_repository.working_dir) / "test.txt" - file_path.write_text("feature changes") - test_repository.index.add(["test.txt"]) - test_repository.index.commit("feature commit") - - result = git_diff(test_repository, default_branch) - - assert "test.txt" in result - assert "feature changes" in result - -def test_git_commit(test_repository): - file_path = Path(test_repository.working_dir) / "commit_test.txt" - file_path.write_text("content to commit") - test_repository.index.add(["commit_test.txt"]) - - result = git_commit(test_repository, "test commit message") - - assert "Changes committed successfully with hash" in result - - latest_commit = test_repository.head.commit - assert latest_commit.message.strip() == "test commit message" - -def test_git_reset(test_repository): - file_path = Path(test_repository.working_dir) / "reset_test.txt" - file_path.write_text("content to reset") - test_repository.index.add(["reset_test.txt"]) - - staged_before = [item.a_path for item in test_repository.index.diff("HEAD")] - assert "reset_test.txt" in staged_before - - result = git_reset(test_repository) - - assert result == "All staged changes reset" - - staged_after = [item.a_path for item in test_repository.index.diff("HEAD")] - assert "reset_test.txt" not in staged_after - -def test_git_log(test_repository): - for i in range(3): - file_path = Path(test_repository.working_dir) / f"log_test_{i}.txt" - file_path.write_text(f"content {i}") - test_repository.index.add([f"log_test_{i}.txt"]) - test_repository.index.commit(f"commit {i}") - - result = git_log(test_repository, max_count=2) - - assert isinstance(result, list) - assert len(result) == 2 - assert "Commit:" in result[0] - assert "Author:" in result[0] - assert "Date:" in result[0] - assert "Message:" in result[0] - -def test_git_log_default(test_repository): - result = git_log(test_repository) - - assert isinstance(result, list) - assert len(result) >= 1 - assert "initial commit" in result[0] - -def test_git_create_branch(test_repository): - result = git_create_branch(test_repository, "new-feature-branch") - - assert "Created branch 'new-feature-branch'" in result - - branches = [ref.name for ref in test_repository.references] - assert "new-feature-branch" in branches - -def test_git_create_branch_from_base(test_repository): - test_repository.git.checkout("-b", "base-branch") - file_path = Path(test_repository.working_dir) / "base.txt" - file_path.write_text("base content") - test_repository.index.add(["base.txt"]) - test_repository.index.commit("base commit") - - result = git_create_branch(test_repository, "derived-branch", "base-branch") - - assert "Created branch 'derived-branch' from 'base-branch'" in result - -def test_git_show(test_repository): - file_path = Path(test_repository.working_dir) / "show_test.txt" - file_path.write_text("show content") - test_repository.index.add(["show_test.txt"]) - test_repository.index.commit("show test commit") - - commit_sha = test_repository.head.commit.hexsha - - result = git_show(test_repository, commit_sha) - - assert "Commit:" in result - assert "Author:" in result - assert "show test commit" in result - assert "show_test.txt" in result - -def test_git_show_initial_commit(test_repository): - initial_commit = list(test_repository.iter_commits())[-1] - - result = git_show(test_repository, initial_commit.hexsha) - - assert "Commit:" in result - assert "initial commit" in result - assert "test.txt" in result - - -# Tests for validate_repo_path (repository scoping security fix) - -def test_validate_repo_path_no_restriction(): - """When no repository restriction is configured, any path should be allowed.""" - validate_repo_path(Path("/any/path"), None) # Should not raise - - -def test_validate_repo_path_exact_match(tmp_path: Path): - """When repo_path exactly matches allowed_repository, validation should pass.""" - allowed = tmp_path / "repo" - allowed.mkdir() - validate_repo_path(allowed, allowed) # Should not raise - - -def test_validate_repo_path_subdirectory(tmp_path: Path): - """When repo_path is a subdirectory of allowed_repository, validation should pass.""" - allowed = tmp_path / "repo" - allowed.mkdir() - subdir = allowed / "subdir" - subdir.mkdir() - validate_repo_path(subdir, allowed) # Should not raise - - -def test_validate_repo_path_outside_allowed(tmp_path: Path): - """When repo_path is outside allowed_repository, validation should raise ValueError.""" - allowed = tmp_path / "allowed_repo" - allowed.mkdir() - outside = tmp_path / "other_repo" - outside.mkdir() - - with pytest.raises(ValueError) as exc_info: - validate_repo_path(outside, allowed) - assert "outside the allowed repository" in str(exc_info.value) - - -def test_validate_repo_path_traversal_attempt(tmp_path: Path): - """Path traversal attempts (../) should be caught and rejected.""" - allowed = tmp_path / "allowed_repo" - allowed.mkdir() - # Attempt to escape via ../ - traversal_path = allowed / ".." / "other_repo" - - with pytest.raises(ValueError) as exc_info: - validate_repo_path(traversal_path, allowed) - assert "outside the allowed repository" in str(exc_info.value) - - -def test_validate_repo_path_symlink_escape(tmp_path: Path): - """Symlinks pointing outside allowed_repository should be rejected.""" - allowed = tmp_path / "allowed_repo" - allowed.mkdir() - outside = tmp_path / "outside" - outside.mkdir() - - # Create a symlink inside allowed that points outside - symlink = allowed / "escape_link" - symlink.symlink_to(outside) - - with pytest.raises(ValueError) as exc_info: - validate_repo_path(symlink, allowed) - assert "outside the allowed repository" in str(exc_info.value) -# Tests for argument injection protection - -def test_git_diff_rejects_flag_injection(test_repository): - """git_diff should reject flags that could be used for argument injection.""" - with pytest.raises(BadName): - git_diff(test_repository, "--output=/tmp/evil") - - with pytest.raises(BadName): - git_diff(test_repository, "--help") - - with pytest.raises(BadName): - git_diff(test_repository, "-p") - - -def test_git_checkout_rejects_flag_injection(test_repository): - """git_checkout should reject flags that could be used for argument injection.""" - with pytest.raises(BadName): - git_checkout(test_repository, "--help") - - with pytest.raises(BadName): - git_checkout(test_repository, "--orphan=evil") - - with pytest.raises(BadName): - git_checkout(test_repository, "-f") - - -def test_git_diff_allows_valid_refs(test_repository): - """git_diff should work normally with valid git refs.""" - # Get the default branch name - default_branch = test_repository.active_branch.name - - # Create a branch with a commit for diffing - test_repository.git.checkout("-b", "valid-diff-branch") - file_path = Path(test_repository.working_dir) / "test.txt" - file_path.write_text("valid diff content") - test_repository.index.add(["test.txt"]) - test_repository.index.commit("valid diff commit") - - # Test with branch name - result = git_diff(test_repository, default_branch) - assert "test.txt" in result - - # Test with HEAD~1 - result = git_diff(test_repository, "HEAD~1") - assert "test.txt" in result - - # Test with commit hash - commit_sha = test_repository.head.commit.hexsha - result = git_diff(test_repository, commit_sha) - assert result is not None - - -def test_git_checkout_allows_valid_branches(test_repository): - """git_checkout should work normally with valid branch names.""" - # Get the default branch name - default_branch = test_repository.active_branch.name - - # Create a branch to checkout - test_repository.git.branch("valid-checkout-branch") - - result = git_checkout(test_repository, "valid-checkout-branch") - assert "Switched to branch 'valid-checkout-branch'" in result - assert test_repository.active_branch.name == "valid-checkout-branch" - - # Checkout back to default branch - result = git_checkout(test_repository, default_branch) - assert "Switched to branch" in result - assert test_repository.active_branch.name == default_branch - - -def test_git_diff_rejects_malicious_refs(test_repository): - """git_diff should reject refs starting with '-' even if they exist. - - This tests defense in depth against an attacker who creates malicious - refs via filesystem manipulation (e.g. using mcp-filesystem to write - to .git/refs/heads/--output=...). - """ - import os - - # Manually create a malicious ref by writing directly to .git/refs - sha = test_repository.head.commit.hexsha - refs_dir = Path(test_repository.git_dir) / "refs" / "heads" - malicious_ref_path = refs_dir / "--output=evil.txt" - malicious_ref_path.write_text(sha) - - # Even though the ref exists, it should be rejected - with pytest.raises(BadName): - git_diff(test_repository, "--output=evil.txt") - - # Verify no file was created (the attack was blocked) - assert not os.path.exists("evil.txt") - - # Cleanup - malicious_ref_path.unlink() - - -def test_git_checkout_rejects_malicious_refs(test_repository): - """git_checkout should reject refs starting with '-' even if they exist.""" - # Manually create a malicious ref - sha = test_repository.head.commit.hexsha - refs_dir = Path(test_repository.git_dir) / "refs" / "heads" - malicious_ref_path = refs_dir / "--orphan=evil" - malicious_ref_path.write_text(sha) - - # Even though the ref exists, it should be rejected - with pytest.raises(BadName): - git_checkout(test_repository, "--orphan=evil") - - # Cleanup - malicious_ref_path.unlink() - - -# Tests for argument injection protection in git_show, git_create_branch, -# git_log, and git_branch โ€” matching the existing guards on git_diff and -# git_checkout. - -def test_git_show_rejects_flag_injection(test_repository): - """git_show should reject revisions starting with '-'.""" - with pytest.raises(BadName): - git_show(test_repository, "--output=/tmp/evil") - - with pytest.raises(BadName): - git_show(test_repository, "-p") - - -def test_git_show_rejects_malicious_refs(test_repository): - """git_show should reject refs starting with '-' even if they exist.""" - sha = test_repository.head.commit.hexsha - refs_dir = Path(test_repository.git_dir) / "refs" / "heads" - malicious_ref_path = refs_dir / "--format=evil" - malicious_ref_path.write_text(sha) - - with pytest.raises(BadName): - git_show(test_repository, "--format=evil") - - malicious_ref_path.unlink() - - -def test_git_create_branch_rejects_flag_injection(test_repository): - """git_create_branch should reject branch names starting with '-'.""" - with pytest.raises(BadName): - git_create_branch(test_repository, "--track=evil") - - with pytest.raises(BadName): - git_create_branch(test_repository, "-f") - - -def test_git_create_branch_rejects_base_branch_flag_injection(test_repository): - """git_create_branch should reject base branch names starting with '-'.""" - with pytest.raises(BadName): - git_create_branch(test_repository, "new-branch", "--track=evil") - - -def test_git_log_rejects_timestamp_flag_injection(test_repository): - """git_log should reject timestamps starting with '-'.""" - with pytest.raises(ValueError): - git_log(test_repository, start_timestamp="--exec=evil") - - with pytest.raises(ValueError): - git_log(test_repository, end_timestamp="--exec=evil") - - -def test_git_branch_rejects_contains_flag_injection(test_repository): - """git_branch should reject contains/not_contains values starting with '-'.""" - with pytest.raises(BadName): - git_branch(test_repository, "local", contains="--exec=evil") - - with pytest.raises(BadName): - git_branch(test_repository, "local", not_contains="--exec=evil") - - -def test_git_log_formatting_no_repr(test_repository): - """Test that git_log does not use !r formatting (no Python object repr or quotes).""" - file_path = Path(test_repository.working_dir) / "multiline.txt" - file_path.write_text("multiline test") - test_repository.index.add(["multiline.txt"]) - test_repository.index.commit("Subject line\n\nDetailed body line 1\nDetailed body line 2") - - result = git_log(test_repository, max_count=1) - entry = result[0] - - # Verify no python repr quotes around commit hash or message - assert "Commit: '" not in entry - assert 'Commit: "' not in entry - assert "= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform != 'win32'", - "python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.14' and sys_platform != 'win32'", -] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] [[package]] @@ -27,109 +20,18 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422, upload-time = "2024-10-14T14:31:44.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377, upload-time = "2024-10-14T14:31:42.623Z" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, ] [[package]] name = "certifi" version = "2024.8.30" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507, upload-time = "2024-08-30T01:55:04.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321, upload-time = "2024-08-30T01:55:02.591Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, ] [[package]] @@ -137,86 +39,29 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "platform_system == 'Windows'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cryptography" -version = "49.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, - { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, - { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, - { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, - { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, - { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, - { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, - { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, - { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, - { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, - { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, - { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, - { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, - { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, - { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, - { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, - { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, - { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, - { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, - { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, - { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] [[package]] @@ -226,43 +71,43 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "smmap" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/0d/bbb5b5ee188dec84647a4664f3e11b06ade2bde568dbd489d9d64adef8ed/gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b", size = 394469, upload-time = "2023-10-20T07:43:19.146Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/0d/bbb5b5ee188dec84647a4664f3e11b06ade2bde568dbd489d9d64adef8ed/gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b", size = 394469 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/5b/8f0c4a5bb9fd491c277c21eff7ccae71b47d43c4446c9d0c6cff2fe8c2c4/gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4", size = 62721, upload-time = "2023-10-20T07:43:16.712Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5b/8f0c4a5bb9fd491c277c21eff7ccae71b47d43c4446c9d0c6cff2fe8c2c4/gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4", size = 62721 }, ] [[package]] name = "gitpython" -version = "3.1.54" +version = "3.1.43" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/d5/3da0b92033887033f4c27f2dd109a303c4ca62813c7b3bb2511edb4777de/gitpython-3.1.54.tar.gz", hash = "sha256:53f2085e24a2cda300eed7c3fc5f1559ae289634b725e98acaf4791940247aa0", size = 225076, upload-time = "2026-07-22T04:08:51.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/a1/106fd9fa2dd989b6fb36e5893961f82992cf676381707253e0bf93eb1662/GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c", size = 214149 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/b9/876f442a28df5c068ca69b0122d5c35e65fd2d2fa9992ea5cb5944ea00a6/gitpython-3.1.54-py3-none-any.whl", hash = "sha256:b90d7b3d9bc0238681d24369130826f0dcdb0ceaa45db67cf1d4ffa4c302dedf", size = 216575, upload-time = "2026-07-22T04:08:50.05Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bd/cc3a402a6439c15c3d4294333e13042b915bbeab54edc457c723931fed3f/GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff", size = 207337 }, ] [[package]] name = "h11" -version = "0.16.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, ] [[package]] name = "httpcore" -version = "1.0.9" +version = "1.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, ] [[package]] @@ -276,93 +121,49 @@ dependencies = [ { name = "idna" }, { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189, upload-time = "2024-08-27T12:54:01.334Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395, upload-time = "2024-08-27T12:53:59.653Z" }, + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, ] [[package]] name = "httpx-sse" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, ] [[package]] name = "idna" -version = "3.18" +version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, -] - -[[package]] -name = "jsonschema" -version = "4.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] [[package]] name = "mcp" -version = "1.29.0" +version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "httpx" }, { name = "httpx-sse" }, - { name = "jsonschema" }, { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "python-multipart" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/d3/f9acc21dfc886e4f78e2add1a47db46ce16884346afde53f8a064c02c891/mcp-1.29.0.tar.gz", hash = "sha256:52d01f334de1868cc3bb2d6604931126a67631f99a6c5d3b82ba47290315ec36", size = 643148, upload-time = "2026-07-28T13:41:41.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/1c/932818470ffd49c33509110c835101a8dc4c9cdd06028b9f647fb3dde237/mcp-0.9.1.tar.gz", hash = "sha256:e8509a37c2ab546095788ed170e0fb4d7ce0cf5a3ee56b6449c78af27321a425", size = 78218 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c8/248b201f6d753d69fd5d6506011abbb35a946d9142b2ae311a948fd0be3d/mcp-1.29.0-py3-none-any.whl", hash = "sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7", size = 223436, upload-time = "2026-07-28T13:41:40.337Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a0/2ee813d456b57a726d583868417d1ad900fbe12ee3c8cd866e3e804ca486/mcp-0.9.1-py3-none-any.whl", hash = "sha256:7f640fcfb0be486aa510594df309920ae1d375cdca1f8aff21db3a96d837f303", size = 31562 }, ] [[package]] name = "mcp-server-git" -version = "0.6.2" +version = "0.4.1" source = { editable = "." } dependencies = [ { name = "click" }, @@ -374,22 +175,20 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "pyright" }, - { name = "pytest" }, { name = "ruff" }, ] [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.1.7" }, - { name = "gitpython", specifier = ">=3.1.50" }, - { name = "mcp", specifier = ">=1.29.0,<2" }, + { name = "gitpython", specifier = ">=3.1.43" }, + { name = "mcp", specifier = ">=0.6.0" }, { name = "pydantic", specifier = ">=2.0.0" }, ] [package.metadata.requires-dev] dev = [ - { name = "pyright", specifier = ">=1.1.407" }, - { name = "pytest", specifier = ">=8.0.0" }, + { name = "pyright", specifier = ">=1.1.389" }, { name = "ruff", specifier = ">=0.7.3" }, ] @@ -397,459 +196,154 @@ dev = [ name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, -] - -[[package]] -name = "packaging" -version = "24.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, -] - -[[package]] -name = "pycparser" -version = "2.23" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] [[package]] name = "pydantic" -version = "2.12.5" +version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, - { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/bd/7fc610993f616d2398958d0028d15eaf53bde5f80cb2edb7aa4f1feaf3a7/pydantic-2.10.1.tar.gz", hash = "sha256:a4daca2dc0aa429555e0656d6bf94873a7dc5f54ee42b1f5873d666fb3f35560", size = 783717 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fc/fda48d347bd50a788dd2a0f318a52160f911b86fc2d8b4c86f4d7c9bceea/pydantic-2.10.1-py3-none-any.whl", hash = "sha256:a8d20db84de64cf4a7d59e899c2caf0fe9d660c7cfc482528e7020d7dd189a7e", size = 455329 }, ] [[package]] name = "pydantic-core" -version = "2.41.5" +version = "2.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.14.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, -] - -[[package]] -name = "pyjwt" -version = "2.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, + { url = "https://files.pythonhosted.org/packages/6e/ce/60fd96895c09738648c83f3f00f595c807cb6735c70d3306b548cc96dd49/pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a", size = 1897984 }, + { url = "https://files.pythonhosted.org/packages/fd/b9/84623d6b6be98cc209b06687d9bca5a7b966ffed008d15225dd0d20cce2e/pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b", size = 1807491 }, + { url = "https://files.pythonhosted.org/packages/01/72/59a70165eabbc93b1111d42df9ca016a4aa109409db04304829377947028/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278", size = 1831953 }, + { url = "https://files.pythonhosted.org/packages/7c/0c/24841136476adafd26f94b45bb718a78cb0500bd7b4f8d667b67c29d7b0d/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05", size = 1856071 }, + { url = "https://files.pythonhosted.org/packages/53/5e/c32957a09cceb2af10d7642df45d1e3dbd8596061f700eac93b801de53c0/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4", size = 2038439 }, + { url = "https://files.pythonhosted.org/packages/e4/8f/979ab3eccd118b638cd6d8f980fea8794f45018255a36044dea40fe579d4/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f", size = 2787416 }, + { url = "https://files.pythonhosted.org/packages/02/1d/00f2e4626565b3b6d3690dab4d4fe1a26edd6a20e53749eb21ca892ef2df/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08", size = 2134548 }, + { url = "https://files.pythonhosted.org/packages/9d/46/3112621204128b90898adc2e721a3cd6cf5626504178d6f32c33b5a43b79/pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6", size = 1989882 }, + { url = "https://files.pythonhosted.org/packages/49/ec/557dd4ff5287ffffdf16a31d08d723de6762bb1b691879dc4423392309bc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807", size = 1995829 }, + { url = "https://files.pythonhosted.org/packages/6e/b2/610dbeb74d8d43921a7234555e4c091cb050a2bdb8cfea86d07791ce01c5/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c", size = 2091257 }, + { url = "https://files.pythonhosted.org/packages/8c/7f/4bf8e9d26a9118521c80b229291fa9558a07cdd9a968ec2d5c1026f14fbc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206", size = 2143894 }, + { url = "https://files.pythonhosted.org/packages/1f/1c/875ac7139c958f4390f23656fe696d1acc8edf45fb81e4831960f12cd6e4/pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c", size = 1816081 }, + { url = "https://files.pythonhosted.org/packages/d7/41/55a117acaeda25ceae51030b518032934f251b1dac3704a53781383e3491/pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17", size = 1981109 }, + { url = "https://files.pythonhosted.org/packages/27/39/46fe47f2ad4746b478ba89c561cafe4428e02b3573df882334bd2964f9cb/pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8", size = 1895553 }, + { url = "https://files.pythonhosted.org/packages/1c/00/0804e84a78b7fdb394fff4c4f429815a10e5e0993e6ae0e0b27dd20379ee/pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330", size = 1807220 }, + { url = "https://files.pythonhosted.org/packages/01/de/df51b3bac9820d38371f5a261020f505025df732ce566c2a2e7970b84c8c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52", size = 1829727 }, + { url = "https://files.pythonhosted.org/packages/5f/d9/c01d19da8f9e9fbdb2bf99f8358d145a312590374d0dc9dd8dbe484a9cde/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4", size = 1854282 }, + { url = "https://files.pythonhosted.org/packages/5f/84/7db66eb12a0dc88c006abd6f3cbbf4232d26adfd827a28638c540d8f871d/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c", size = 2037437 }, + { url = "https://files.pythonhosted.org/packages/34/ac/a2537958db8299fbabed81167d58cc1506049dba4163433524e06a7d9f4c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de", size = 2780899 }, + { url = "https://files.pythonhosted.org/packages/4a/c1/3e38cd777ef832c4fdce11d204592e135ddeedb6c6f525478a53d1c7d3e5/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025", size = 2135022 }, + { url = "https://files.pythonhosted.org/packages/7a/69/b9952829f80fd555fe04340539d90e000a146f2a003d3fcd1e7077c06c71/pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e", size = 1987969 }, + { url = "https://files.pythonhosted.org/packages/05/72/257b5824d7988af43460c4e22b63932ed651fe98804cc2793068de7ec554/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919", size = 1994625 }, + { url = "https://files.pythonhosted.org/packages/73/c3/78ed6b7f3278a36589bcdd01243189ade7fc9b26852844938b4d7693895b/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c", size = 2090089 }, + { url = "https://files.pythonhosted.org/packages/8d/c8/b4139b2f78579960353c4cd987e035108c93a78371bb19ba0dc1ac3b3220/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc", size = 2142496 }, + { url = "https://files.pythonhosted.org/packages/3e/f8/171a03e97eb36c0b51981efe0f78460554a1d8311773d3d30e20c005164e/pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9", size = 1811758 }, + { url = "https://files.pythonhosted.org/packages/6a/fe/4e0e63c418c1c76e33974a05266e5633e879d4061f9533b1706a86f77d5b/pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5", size = 1980864 }, + { url = "https://files.pythonhosted.org/packages/50/fc/93f7238a514c155a8ec02fc7ac6376177d449848115e4519b853820436c5/pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89", size = 1864327 }, + { url = "https://files.pythonhosted.org/packages/be/51/2e9b3788feb2aebff2aa9dfbf060ec739b38c05c46847601134cc1fed2ea/pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", size = 1895239 }, + { url = "https://files.pythonhosted.org/packages/7b/9e/f8063952e4a7d0127f5d1181addef9377505dcce3be224263b25c4f0bfd9/pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", size = 1805070 }, + { url = "https://files.pythonhosted.org/packages/2c/9d/e1d6c4561d262b52e41b17a7ef8301e2ba80b61e32e94520271029feb5d8/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", size = 1828096 }, + { url = "https://files.pythonhosted.org/packages/be/65/80ff46de4266560baa4332ae3181fffc4488ea7d37282da1a62d10ab89a4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", size = 1857708 }, + { url = "https://files.pythonhosted.org/packages/d5/ca/3370074ad758b04d9562b12ecdb088597f4d9d13893a48a583fb47682cdf/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", size = 2037751 }, + { url = "https://files.pythonhosted.org/packages/b1/e2/4ab72d93367194317b99d051947c071aef6e3eb95f7553eaa4208ecf9ba4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", size = 2733863 }, + { url = "https://files.pythonhosted.org/packages/8a/c6/8ae0831bf77f356bb73127ce5a95fe115b10f820ea480abbd72d3cc7ccf3/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", size = 2161161 }, + { url = "https://files.pythonhosted.org/packages/f1/f4/b2fe73241da2429400fc27ddeaa43e35562f96cf5b67499b2de52b528cad/pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", size = 1993294 }, + { url = "https://files.pythonhosted.org/packages/77/29/4bb008823a7f4cc05828198153f9753b3bd4c104d93b8e0b1bfe4e187540/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", size = 2001468 }, + { url = "https://files.pythonhosted.org/packages/f2/a9/0eaceeba41b9fad851a4107e0cf999a34ae8f0d0d1f829e2574f3d8897b0/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", size = 2091413 }, + { url = "https://files.pythonhosted.org/packages/d8/36/eb8697729725bc610fd73940f0d860d791dc2ad557faaefcbb3edbd2b349/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", size = 2154735 }, + { url = "https://files.pythonhosted.org/packages/52/e5/4f0fbd5c5995cc70d3afed1b5c754055bb67908f55b5cb8000f7112749bf/pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", size = 1833633 }, + { url = "https://files.pythonhosted.org/packages/ee/f2/c61486eee27cae5ac781305658779b4a6b45f9cc9d02c90cb21b940e82cc/pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", size = 1986973 }, + { url = "https://files.pythonhosted.org/packages/df/a6/e3f12ff25f250b02f7c51be89a294689d175ac76e1096c32bf278f29ca1e/pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", size = 1883215 }, + { url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033 }, + { url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542 }, + { url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854 }, + { url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389 }, + { url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934 }, + { url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176 }, + { url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720 }, + { url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972 }, + { url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477 }, + { url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186 }, + { url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429 }, + { url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713 }, + { url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897 }, + { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 }, + { url = "https://files.pythonhosted.org/packages/7c/60/e5eb2d462595ba1f622edbe7b1d19531e510c05c405f0b87c80c1e89d5b1/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6", size = 1894016 }, + { url = "https://files.pythonhosted.org/packages/61/20/da7059855225038c1c4326a840908cc7ca72c7198cb6addb8b92ec81c1d6/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676", size = 1771648 }, + { url = "https://files.pythonhosted.org/packages/8f/fc/5485cf0b0bb38da31d1d292160a4d123b5977841ddc1122c671a30b76cfd/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d", size = 1826929 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/fb1284a210e13a5f34c639efc54d51da136074ffbe25ec0c279cf9fbb1c4/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c", size = 1980591 }, + { url = "https://files.pythonhosted.org/packages/f1/14/77c1887a182d05af74f6aeac7b740da3a74155d3093ccc7ee10b900cc6b5/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27", size = 1981326 }, + { url = "https://files.pythonhosted.org/packages/06/aa/6f1b2747f811a9c66b5ef39d7f02fbb200479784c75e98290d70004b1253/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f", size = 1989205 }, + { url = "https://files.pythonhosted.org/packages/7a/d2/8ce2b074d6835f3c88d85f6d8a399790043e9fdb3d0e43455e72d19df8cc/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed", size = 2079616 }, + { url = "https://files.pythonhosted.org/packages/65/71/af01033d4e58484c3db1e5d13e751ba5e3d6b87cc3368533df4c50932c8b/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f", size = 2133265 }, + { url = "https://files.pythonhosted.org/packages/33/72/f881b5e18fbb67cf2fb4ab253660de3c6899dbb2dba409d0b757e3559e3d/pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c", size = 2001864 }, ] [[package]] name = "pyright" -version = "1.1.407" +version = "1.1.389" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/4e/9a5ab8745e7606b88c2c7ca223449ac9d82a71fd5e31df47b453f2cb39a1/pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220", size = 21940 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, -] - -[[package]] -name = "python-dotenv" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.32" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, -] - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, - { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, - { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, - { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, -] - -[[package]] -name = "rpds-py" -version = "0.30.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, - { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, - { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, - { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, - { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, - { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, - { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, - { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, - { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, - { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, - { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, - { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, - { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, - { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, - { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, - { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, - { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, - { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, - { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, - { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, - { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, - { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, - { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, - { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, - { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, - { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, + { url = "https://files.pythonhosted.org/packages/1b/26/c288cabf8cfc5a27e1aa9e5029b7682c0f920b8074f45d22bf844314d66a/pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60", size = 18581 }, ] [[package]] name = "ruff" version = "0.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537, upload-time = "2024-11-22T10:07:56.012Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283, upload-time = "2024-11-22T10:07:07.866Z" }, - { url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691, upload-time = "2024-11-22T10:07:10.246Z" }, - { url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999, upload-time = "2024-11-22T10:07:13.132Z" }, - { url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437, upload-time = "2024-11-22T10:07:15.499Z" }, - { url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156, upload-time = "2024-11-22T10:07:18.058Z" }, - { url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819, upload-time = "2024-11-22T10:07:20.991Z" }, - { url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927, upload-time = "2024-11-22T10:07:24.327Z" }, - { url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702, upload-time = "2024-11-22T10:07:27.459Z" }, - { url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936, upload-time = "2024-11-22T10:07:29.959Z" }, - { url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488, upload-time = "2024-11-22T10:07:33.139Z" }, - { url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474, upload-time = "2024-11-22T10:07:35.427Z" }, - { url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029, upload-time = "2024-11-22T10:07:38.639Z" }, - { url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481, upload-time = "2024-11-22T10:07:41Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117, upload-time = "2024-11-22T10:07:43.989Z" }, - { url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511, upload-time = "2024-11-22T10:07:47.494Z" }, - { url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876, upload-time = "2024-11-22T10:07:50.138Z" }, - { url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733, upload-time = "2024-11-22T10:07:53.04Z" }, + { url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 }, + { url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 }, + { url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 }, + { url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 }, + { url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 }, + { url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 }, + { url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 }, + { url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 }, + { url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 }, + { url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 }, + { url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 }, + { url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 }, + { url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 }, + { url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 }, + { url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 }, + { url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 }, + { url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 }, ] [[package]] name = "smmap" version = "5.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/04/b5bf6d21dc4041000ccba7eb17dd3055feb237e7ffc2c20d3fae3af62baa/smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", size = 22291, upload-time = "2023-09-17T11:35:05.241Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/04/b5bf6d21dc4041000ccba7eb17dd3055feb237e7ffc2c20d3fae3af62baa/smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", size = 22291 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/a5/10f97f73544edcdef54409f1d839f6049a0d79df68adbc1ceb24d1aaca42/smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da", size = 24282, upload-time = "2023-09-17T11:35:03.253Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a5/10f97f73544edcdef54409f1d839f6049a0d79df68adbc1ceb24d1aaca42/smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da", size = 24282 }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] [[package]] @@ -861,82 +355,30 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383, upload-time = "2024-08-01T08:52:48.659Z" }, + { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383 }, ] [[package]] name = "starlette" -version = "1.3.1" +version = "0.41.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, -] - -[[package]] -name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 }, ] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.12.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] [[package]] @@ -948,7 +390,7 @@ dependencies = [ { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630, upload-time = "2024-11-20T19:41:13.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828, upload-time = "2024-11-20T19:41:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 }, ] diff --git a/src/github/README.md b/src/github/README.md new file mode 100644 index 0000000..cfd268a --- /dev/null +++ b/src/github/README.md @@ -0,0 +1,137 @@ +# GitHub MCP Server + +MCP Server for the GitHub API, enabling file operations, repository management, and more. + +### Features + +- **Automatic Branch Creation**: When creating/updating files or pushing changes, branches are automatically created if they don't exist +- **Comprehensive Error Handling**: Clear error messages for common issues +- **Git History Preservation**: Operations maintain proper Git history without force pushing +- **Batch Operations**: Support for both single-file and multi-file operations + + +## Tools + +1. `create_or_update_file` + - Create or update a single file in a repository + - Inputs: + - `owner` (string): Repository owner (username or organization) + - `repo` (string): Repository name + - `path` (string): Path where to create/update the file + - `content` (string): Content of the file + - `message` (string): Commit message + - `branch` (string): Branch to create/update the file in + - `sha` (optional string): SHA of file being replaced (for updates) + - Returns: File content and commit details + +2. `push_files` + - Push multiple files in a single commit + - Inputs: + - `owner` (string): Repository owner + - `repo` (string): Repository name + - `branch` (string): Branch to push to + - `files` (array): Files to push, each with `path` and `content` + - `message` (string): Commit message + - Returns: Updated branch reference + +3. `search_repositories` + - Search for GitHub repositories + - Inputs: + - `query` (string): Search query + - `page` (optional number): Page number for pagination + - `perPage` (optional number): Results per page (max 100) + - Returns: Repository search results + +4. `create_repository` + - Create a new GitHub repository + - Inputs: + - `name` (string): Repository name + - `description` (optional string): Repository description + - `private` (optional boolean): Whether repo should be private + - `autoInit` (optional boolean): Initialize with README + - Returns: Created repository details + +5. `get_file_contents` + - Get contents of a file or directory + - Inputs: + - `owner` (string): Repository owner + - `repo` (string): Repository name + - `path` (string): Path to file/directory + - `branch` (optional string): Branch to get contents from + - Returns: File/directory contents + +6. `create_issue` + - Create a new issue + - Inputs: + - `owner` (string): Repository owner + - `repo` (string): Repository name + - `title` (string): Issue title + - `body` (optional string): Issue description + - `assignees` (optional string[]): Usernames to assign + - `labels` (optional string[]): Labels to add + - `milestone` (optional number): Milestone number + - Returns: Created issue details + +7. `create_pull_request` + - Create a new pull request + - Inputs: + - `owner` (string): Repository owner + - `repo` (string): Repository name + - `title` (string): PR title + - `body` (optional string): PR description + - `head` (string): Branch containing changes + - `base` (string): Branch to merge into + - `draft` (optional boolean): Create as draft PR + - `maintainer_can_modify` (optional boolean): Allow maintainer edits + - Returns: Created pull request details + +8. `fork_repository` + - Fork a repository + - Inputs: + - `owner` (string): Repository owner + - `repo` (string): Repository name + - `organization` (optional string): Organization to fork to + - Returns: Forked repository details + +9. `create_branch` + - Create a new branch + - Inputs: + - `owner` (string): Repository owner + - `repo` (string): Repository name + - `branch` (string): Name for new branch + - `from_branch` (optional string): Source branch (defaults to repo default) + - Returns: Created branch reference + +## Setup + +### Personal Access Token +[Create a GitHub Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) with appropriate permissions: + - Go to [Personal access tokens](https://github.com/settings/tokens) (in GitHub Settings > Developer settings) + - Select which repositories you'd like this token to have access to (Public, All, or Select) + - Create a token with the `repo` scope ("Full control of private repositories") + - Alternatively, if working only with public repositories, select only the `public_repo` scope + - Copy the generated token + +### Usage with Claude Desktop +To use this with Claude Desktop, add the following to your `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "github": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-github" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "" + } + } + } +} +``` + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/github/index.ts b/src/github/index.ts new file mode 100644 index 0000000..0676a34 --- /dev/null +++ b/src/github/index.ts @@ -0,0 +1,646 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import fetch from "node-fetch"; +import { + GitHubForkSchema, + GitHubReferenceSchema, + GitHubRepositorySchema, + GitHubIssueSchema, + GitHubPullRequestSchema, + GitHubContentSchema, + GitHubCreateUpdateFileResponseSchema, + GitHubSearchResponseSchema, + GitHubTreeSchema, + GitHubCommitSchema, + CreateRepositoryOptionsSchema, + CreateIssueOptionsSchema, + CreatePullRequestOptionsSchema, + CreateBranchOptionsSchema, + type GitHubFork, + type GitHubReference, + type GitHubRepository, + type GitHubIssue, + type GitHubPullRequest, + type GitHubContent, + type GitHubCreateUpdateFileResponse, + type GitHubSearchResponse, + type GitHubTree, + type GitHubCommit, + type FileOperation, + CreateOrUpdateFileSchema, + SearchRepositoriesSchema, + CreateRepositorySchema, + GetFileContentsSchema, + PushFilesSchema, + CreateIssueSchema, + CreatePullRequestSchema, + ForkRepositorySchema, + CreateBranchSchema +} from './schemas.js'; +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +const server = new Server({ + name: "github-mcp-server", + version: "0.1.0", +}, { + capabilities: { + tools: {} + } +}); + +const GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN; + +if (!GITHUB_PERSONAL_ACCESS_TOKEN) { + console.error("GITHUB_PERSONAL_ACCESS_TOKEN environment variable is not set"); + process.exit(1); +} + +async function forkRepository( + owner: string, + repo: string, + organization?: string +): Promise { + const url = organization + ? `https://api.github.com/repos/${owner}/${repo}/forks?organization=${organization}` + : `https://api.github.com/repos/${owner}/${repo}/forks`; + + const response = await fetch(url, { + method: "POST", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server" + } + }); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubForkSchema.parse(await response.json()); +} + +async function createBranch( + owner: string, + repo: string, + options: z.infer +): Promise { + const fullRef = `refs/heads/${options.ref}`; + + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/git/refs`, + { + method: "POST", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + ref: fullRef, + sha: options.sha + }) + } + ); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubReferenceSchema.parse(await response.json()); +} + +async function getDefaultBranchSHA( + owner: string, + repo: string +): Promise { + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`, + { + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server" + } + } + ); + + if (!response.ok) { + const masterResponse = await fetch( + `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`, + { + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server" + } + } + ); + + if (!masterResponse.ok) { + throw new Error("Could not find default branch (tried 'main' and 'master')"); + } + + const data = GitHubReferenceSchema.parse(await masterResponse.json()); + return data.object.sha; + } + + const data = GitHubReferenceSchema.parse(await response.json()); + return data.object.sha; +} + +async function getFileContents( + owner: string, + repo: string, + path: string, + branch?: string +): Promise { + let url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`; + if (branch) { + url += `?ref=${branch}`; + } + + const response = await fetch(url, { + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server" + } + }); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + const data = GitHubContentSchema.parse(await response.json()); + + // If it's a file, decode the content + if (!Array.isArray(data) && data.content) { + data.content = Buffer.from(data.content, 'base64').toString('utf8'); + } + + return data; +} + +async function createIssue( + owner: string, + repo: string, + options: z.infer +): Promise { + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/issues`, + { + method: "POST", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + "Content-Type": "application/json" + }, + body: JSON.stringify(options) + } + ); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubIssueSchema.parse(await response.json()); +} + +async function createPullRequest( + owner: string, + repo: string, + options: z.infer +): Promise { + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/pulls`, + { + method: "POST", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + "Content-Type": "application/json" + }, + body: JSON.stringify(options) + } + ); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubPullRequestSchema.parse(await response.json()); +} + +async function createOrUpdateFile( + owner: string, + repo: string, + path: string, + content: string, + message: string, + branch: string, + sha?: string +): Promise { + const encodedContent = Buffer.from(content).toString('base64'); + + let currentSha = sha; + if (!currentSha) { + try { + const existingFile = await getFileContents(owner, repo, path, branch); + if (!Array.isArray(existingFile)) { + currentSha = existingFile.sha; + } + } catch (error) { + console.error('Note: File does not exist in branch, will create new file'); + } + } + + const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`; + + const body = { + message, + content: encodedContent, + branch, + ...(currentSha ? { sha: currentSha } : {}) + }; + + const response = await fetch(url, { + method: "PUT", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + "Content-Type": "application/json" + }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubCreateUpdateFileResponseSchema.parse(await response.json()); +} + +async function createTree( + owner: string, + repo: string, + files: FileOperation[], + baseTree?: string +): Promise { + const tree = files.map(file => ({ + path: file.path, + mode: '100644' as const, + type: 'blob' as const, + content: file.content + })); + + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/git/trees`, + { + method: "POST", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + tree, + base_tree: baseTree + }) + } + ); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubTreeSchema.parse(await response.json()); +} + +async function createCommit( + owner: string, + repo: string, + message: string, + tree: string, + parents: string[] +): Promise { + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/git/commits`, + { + method: "POST", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + message, + tree, + parents + }) + } + ); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubCommitSchema.parse(await response.json()); +} + +async function updateReference( + owner: string, + repo: string, + ref: string, + sha: string +): Promise { + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/git/refs/${ref}`, + { + method: "PATCH", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + sha, + force: true + }) + } + ); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubReferenceSchema.parse(await response.json()); +} + +async function pushFiles( + owner: string, + repo: string, + branch: string, + files: FileOperation[], + message: string +): Promise { + const refResponse = await fetch( + `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, + { + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server" + } + } + ); + + if (!refResponse.ok) { + throw new Error(`GitHub API error: ${refResponse.statusText}`); + } + + const ref = GitHubReferenceSchema.parse(await refResponse.json()); + const commitSha = ref.object.sha; + + const tree = await createTree(owner, repo, files, commitSha); + const commit = await createCommit(owner, repo, message, tree.sha, [commitSha]); + return await updateReference(owner, repo, `heads/${branch}`, commit.sha); +} + +async function searchRepositories( + query: string, + page: number = 1, + perPage: number = 30 +): Promise { + const url = new URL("https://api.github.com/search/repositories"); + url.searchParams.append("q", query); + url.searchParams.append("page", page.toString()); + url.searchParams.append("per_page", perPage.toString()); + + const response = await fetch(url.toString(), { + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server" + } + }); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubSearchResponseSchema.parse(await response.json()); +} + +async function createRepository( + options: z.infer +): Promise { + const response = await fetch("https://api.github.com/user/repos", { + method: "POST", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + "Content-Type": "application/json" + }, + body: JSON.stringify(options) + }); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubRepositorySchema.parse(await response.json()); +} + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "create_or_update_file", + description: "Create or update a single file in a GitHub repository", + inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema) + }, + { + name: "search_repositories", + description: "Search for GitHub repositories", + inputSchema: zodToJsonSchema(SearchRepositoriesSchema) + }, + { + name: "create_repository", + description: "Create a new GitHub repository in your account", + inputSchema: zodToJsonSchema(CreateRepositorySchema) + }, + { + name: "get_file_contents", + description: "Get the contents of a file or directory from a GitHub repository", + inputSchema: zodToJsonSchema(GetFileContentsSchema) + }, + { + name: "push_files", + description: "Push multiple files to a GitHub repository in a single commit", + inputSchema: zodToJsonSchema(PushFilesSchema) + }, + { + name: "create_issue", + description: "Create a new issue in a GitHub repository", + inputSchema: zodToJsonSchema(CreateIssueSchema) + }, + { + name: "create_pull_request", + description: "Create a new pull request in a GitHub repository", + inputSchema: zodToJsonSchema(CreatePullRequestSchema) + }, + { + name: "fork_repository", + description: "Fork a GitHub repository to your account or specified organization", + inputSchema: zodToJsonSchema(ForkRepositorySchema) + }, + { + name: "create_branch", + description: "Create a new branch in a GitHub repository", + inputSchema: zodToJsonSchema(CreateBranchSchema) + } + ] + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + try { + if (!request.params.arguments) { + throw new Error("Arguments are required"); + } + + switch (request.params.name) { + case "fork_repository": { + const args = ForkRepositorySchema.parse(request.params.arguments); + const fork = await forkRepository(args.owner, args.repo, args.organization); + return { toolResult: fork }; + } + + case "create_branch": { + const args = CreateBranchSchema.parse(request.params.arguments); + let sha: string; + if (args.from_branch) { + const response = await fetch( + `https://api.github.com/repos/${args.owner}/${args.repo}/git/refs/heads/${args.from_branch}`, + { + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server" + } + } + ); + + if (!response.ok) { + throw new Error(`Source branch '${args.from_branch}' not found`); + } + + const data = GitHubReferenceSchema.parse(await response.json()); + sha = data.object.sha; + } else { + sha = await getDefaultBranchSHA(args.owner, args.repo); + } + + const branch = await createBranch(args.owner, args.repo, { + ref: args.branch, + sha + }); + + return { toolResult: branch }; + } + + case "search_repositories": { + const args = SearchRepositoriesSchema.parse(request.params.arguments); + const results = await searchRepositories(args.query, args.page, args.perPage); + return { toolResult: results }; + } + + case "create_repository": { + const args = CreateRepositorySchema.parse(request.params.arguments); + const repository = await createRepository(args); + return { toolResult: repository }; + } + + case "get_file_contents": { + const args = GetFileContentsSchema.parse(request.params.arguments); + const contents = await getFileContents(args.owner, args.repo, args.path, args.branch); + return { toolResult: contents }; + } + + case "create_or_update_file": { + const args = CreateOrUpdateFileSchema.parse(request.params.arguments); + const result = await createOrUpdateFile( + args.owner, + args.repo, + args.path, + args.content, + args.message, + args.branch, + args.sha + ); + return { toolResult: result }; + } + + case "push_files": { + const args = PushFilesSchema.parse(request.params.arguments); + const result = await pushFiles( + args.owner, + args.repo, + args.branch, + args.files, + args.message + ); + return { toolResult: result }; + } + + case "create_issue": { + const args = CreateIssueSchema.parse(request.params.arguments); + const { owner, repo, ...options } = args; + const issue = await createIssue(owner, repo, options); + return { toolResult: issue }; + } + + case "create_pull_request": { + const args = CreatePullRequestSchema.parse(request.params.arguments); + const { owner, repo, ...options } = args; + const pullRequest = await createPullRequest(owner, repo, options); + return { toolResult: pullRequest }; + } + + default: + throw new Error(`Unknown tool: ${request.params.name}`); + } + } catch (error) { + if (error instanceof z.ZodError) { + throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`); + } + throw error; + } +}); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("GitHub MCP Server running on stdio"); +} + +runServer().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); +}); \ No newline at end of file diff --git a/src/github/package-lock.json b/src/github/package-lock.json new file mode 100644 index 0000000..fc0d396 --- /dev/null +++ b/src/github/package-lock.json @@ -0,0 +1,551 @@ +{ + "name": "@modelcontextprotocol/server-github", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@modelcontextprotocol/server-github", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2" + }, + "bin": { + "mcp-server-github": "dist/index.js" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", + "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@types/node": { + "version": "22.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", + "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/src/github/package.json b/src/github/package.json new file mode 100644 index 0000000..bc7710e --- /dev/null +++ b/src/github/package.json @@ -0,0 +1,31 @@ +{ + "name": "@modelcontextprotocol/server-github", + "version": "0.5.1", + "description": "MCP server for using the GitHub API", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-github": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2", + "zod-to-json-schema": "^3.23.5" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/github/schemas.ts b/src/github/schemas.ts new file mode 100644 index 0000000..213458e --- /dev/null +++ b/src/github/schemas.ts @@ -0,0 +1,378 @@ +import { z } from 'zod'; + +// Base schemas for common types +export const GitHubAuthorSchema = z.object({ + name: z.string(), + email: z.string(), + date: z.string() +}); + +// Repository related schemas +export const GitHubOwnerSchema = z.object({ + login: z.string(), + id: z.number(), + node_id: z.string(), + avatar_url: z.string(), + url: z.string(), + html_url: z.string(), + type: z.string() +}); + +export const GitHubRepositorySchema = z.object({ + id: z.number(), + node_id: z.string(), + name: z.string(), + full_name: z.string(), + private: z.boolean(), + owner: GitHubOwnerSchema, + html_url: z.string(), + description: z.string().nullable(), + fork: z.boolean(), + url: z.string(), + created_at: z.string(), + updated_at: z.string(), + pushed_at: z.string(), + git_url: z.string(), + ssh_url: z.string(), + clone_url: z.string(), + default_branch: z.string() +}); + +// File content schemas +export const GitHubFileContentSchema = z.object({ + type: z.string(), + encoding: z.string(), + size: z.number(), + name: z.string(), + path: z.string(), + content: z.string(), + sha: z.string(), + url: z.string(), + git_url: z.string(), + html_url: z.string(), + download_url: z.string() +}); + +export const GitHubDirectoryContentSchema = z.object({ + type: z.string(), + size: z.number(), + name: z.string(), + path: z.string(), + sha: z.string(), + url: z.string(), + git_url: z.string(), + html_url: z.string(), + download_url: z.string().nullable() +}); + +export const GitHubContentSchema = z.union([ + GitHubFileContentSchema, + z.array(GitHubDirectoryContentSchema) +]); + +// Operation schemas +export const FileOperationSchema = z.object({ + path: z.string(), + content: z.string() +}); + +// Tree and commit schemas +export const GitHubTreeEntrySchema = z.object({ + path: z.string(), + mode: z.enum(['100644', '100755', '040000', '160000', '120000']), + type: z.enum(['blob', 'tree', 'commit']), + size: z.number().optional(), + sha: z.string(), + url: z.string() +}); + +export const GitHubTreeSchema = z.object({ + sha: z.string(), + url: z.string(), + tree: z.array(GitHubTreeEntrySchema), + truncated: z.boolean() +}); + +export const GitHubCommitSchema = z.object({ + sha: z.string(), + node_id: z.string(), + url: z.string(), + author: GitHubAuthorSchema, + committer: GitHubAuthorSchema, + message: z.string(), + tree: z.object({ + sha: z.string(), + url: z.string() + }), + parents: z.array(z.object({ + sha: z.string(), + url: z.string() + })) +}); + +// Reference schema +export const GitHubReferenceSchema = z.object({ + ref: z.string(), + node_id: z.string(), + url: z.string(), + object: z.object({ + sha: z.string(), + type: z.string(), + url: z.string() + }) +}); + +// Input schemas for operations +export const CreateRepositoryOptionsSchema = z.object({ + name: z.string(), + description: z.string().optional(), + private: z.boolean().optional(), + auto_init: z.boolean().optional() +}); + +export const CreateIssueOptionsSchema = z.object({ + title: z.string(), + body: z.string().optional(), + assignees: z.array(z.string()).optional(), + milestone: z.number().optional(), + labels: z.array(z.string()).optional() +}); + +export const CreatePullRequestOptionsSchema = z.object({ + title: z.string(), + body: z.string().optional(), + head: z.string(), + base: z.string(), + maintainer_can_modify: z.boolean().optional(), + draft: z.boolean().optional() +}); + +export const CreateBranchOptionsSchema = z.object({ + ref: z.string(), + sha: z.string() +}); + +// Response schemas for operations +export const GitHubCreateUpdateFileResponseSchema = z.object({ + content: GitHubFileContentSchema.nullable(), + commit: z.object({ + sha: z.string(), + node_id: z.string(), + url: z.string(), + html_url: z.string(), + author: GitHubAuthorSchema, + committer: GitHubAuthorSchema, + message: z.string(), + tree: z.object({ + sha: z.string(), + url: z.string() + }), + parents: z.array(z.object({ + sha: z.string(), + url: z.string(), + html_url: z.string() + })) + }) +}); + +export const GitHubSearchResponseSchema = z.object({ + total_count: z.number(), + incomplete_results: z.boolean(), + items: z.array(GitHubRepositorySchema) +}); + +// Fork related schemas +export const GitHubForkParentSchema = z.object({ + name: z.string(), + full_name: z.string(), + owner: z.object({ + login: z.string(), + id: z.number(), + avatar_url: z.string() + }), + html_url: z.string() +}); + +export const GitHubForkSchema = GitHubRepositorySchema.extend({ + parent: GitHubForkParentSchema, + source: GitHubForkParentSchema +}); + +// Issue related schemas +export const GitHubLabelSchema = z.object({ + id: z.number(), + node_id: z.string(), + url: z.string(), + name: z.string(), + color: z.string(), + default: z.boolean(), + description: z.string().optional() +}); + +export const GitHubIssueAssigneeSchema = z.object({ + login: z.string(), + id: z.number(), + avatar_url: z.string(), + url: z.string(), + html_url: z.string() +}); + +export const GitHubMilestoneSchema = z.object({ + url: z.string(), + html_url: z.string(), + labels_url: z.string(), + id: z.number(), + node_id: z.string(), + number: z.number(), + title: z.string(), + description: z.string(), + state: z.string() +}); + +export const GitHubIssueSchema = z.object({ + url: z.string(), + repository_url: z.string(), + labels_url: z.string(), + comments_url: z.string(), + events_url: z.string(), + html_url: z.string(), + id: z.number(), + node_id: z.string(), + number: z.number(), + title: z.string(), + user: GitHubIssueAssigneeSchema, + labels: z.array(GitHubLabelSchema), + state: z.string(), + locked: z.boolean(), + assignee: GitHubIssueAssigneeSchema.nullable(), + assignees: z.array(GitHubIssueAssigneeSchema), + milestone: GitHubMilestoneSchema.nullable(), + comments: z.number(), + created_at: z.string(), + updated_at: z.string(), + closed_at: z.string().nullable(), + body: z.string() +}); + +// Pull Request related schemas +export const GitHubPullRequestHeadSchema = z.object({ + label: z.string(), + ref: z.string(), + sha: z.string(), + user: GitHubIssueAssigneeSchema, + repo: GitHubRepositorySchema +}); + +export const GitHubPullRequestSchema = z.object({ + url: z.string(), + id: z.number(), + node_id: z.string(), + html_url: z.string(), + diff_url: z.string(), + patch_url: z.string(), + issue_url: z.string(), + number: z.number(), + state: z.string(), + locked: z.boolean(), + title: z.string(), + user: GitHubIssueAssigneeSchema, + body: z.string(), + created_at: z.string(), + updated_at: z.string(), + closed_at: z.string().nullable(), + merged_at: z.string().nullable(), + merge_commit_sha: z.string(), + assignee: GitHubIssueAssigneeSchema.nullable(), + assignees: z.array(GitHubIssueAssigneeSchema), + head: GitHubPullRequestHeadSchema, + base: GitHubPullRequestHeadSchema +}); + +const RepoParamsSchema = z.object({ + owner: z.string().describe("Repository owner (username or organization)"), + repo: z.string().describe("Repository name") +}); + +export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({ + path: z.string().describe("Path where to create/update the file"), + content: z.string().describe("Content of the file"), + message: z.string().describe("Commit message"), + branch: z.string().describe("Branch to create/update the file in"), + sha: z.string().optional() + .describe("SHA of the file being replaced (required when updating existing files)") +}); + +export const SearchRepositoriesSchema = z.object({ + query: z.string().describe("Search query (see GitHub search syntax)"), + page: z.number().optional().describe("Page number for pagination (default: 1)"), + perPage: z.number().optional().describe("Number of results per page (default: 30, max: 100)") +}); + +export const CreateRepositorySchema = z.object({ + name: z.string().describe("Repository name"), + description: z.string().optional().describe("Repository description"), + private: z.boolean().optional().describe("Whether the repository should be private"), + autoInit: z.boolean().optional().describe("Initialize with README.md") +}); + +export const GetFileContentsSchema = RepoParamsSchema.extend({ + path: z.string().describe("Path to the file or directory"), + branch: z.string().optional().describe("Branch to get contents from") +}); + +export const PushFilesSchema = RepoParamsSchema.extend({ + branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"), + files: z.array(z.object({ + path: z.string().describe("Path where to create the file"), + content: z.string().describe("Content of the file") + })).describe("Array of files to push"), + message: z.string().describe("Commit message") +}); + +export const CreateIssueSchema = RepoParamsSchema.extend({ + title: z.string().describe("Issue title"), + body: z.string().optional().describe("Issue body/description"), + assignees: z.array(z.string()).optional().describe("Array of usernames to assign"), + labels: z.array(z.string()).optional().describe("Array of label names"), + milestone: z.number().optional().describe("Milestone number to assign") +}); + +export const CreatePullRequestSchema = RepoParamsSchema.extend({ + title: z.string().describe("Pull request title"), + body: z.string().optional().describe("Pull request body/description"), + head: z.string().describe("The name of the branch where your changes are implemented"), + base: z.string().describe("The name of the branch you want the changes pulled into"), + draft: z.boolean().optional().describe("Whether to create the pull request as a draft"), + maintainer_can_modify: z.boolean().optional() + .describe("Whether maintainers can modify the pull request") +}); + +export const ForkRepositorySchema = RepoParamsSchema.extend({ + organization: z.string().optional() + .describe("Optional: organization to fork to (defaults to your personal account)") +}); + +export const CreateBranchSchema = RepoParamsSchema.extend({ + branch: z.string().describe("Name for the new branch"), + from_branch: z.string().optional() + .describe("Optional: source branch to create from (defaults to the repository's default branch)") +}); + +// Export types +export type GitHubAuthor = z.infer; +export type GitHubFork = z.infer; +export type GitHubIssue = z.infer; +export type GitHubPullRequest = z.infer;export type GitHubRepository = z.infer; +export type GitHubFileContent = z.infer; +export type GitHubDirectoryContent = z.infer; +export type GitHubContent = z.infer; +export type FileOperation = z.infer; +export type GitHubTree = z.infer; +export type GitHubCommit = z.infer; +export type GitHubReference = z.infer; +export type CreateRepositoryOptions = z.infer; +export type CreateIssueOptions = z.infer; +export type CreatePullRequestOptions = z.infer; +export type CreateBranchOptions = z.infer; +export type GitHubCreateUpdateFileResponse = z.infer; +export type GitHubSearchResponse = z.infer; \ No newline at end of file diff --git a/src/github/tsconfig.json b/src/github/tsconfig.json new file mode 100644 index 0000000..4d33cae --- /dev/null +++ b/src/github/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] + } + \ No newline at end of file diff --git a/src/gitlab/README.md b/src/gitlab/README.md new file mode 100644 index 0000000..fdf8255 --- /dev/null +++ b/src/gitlab/README.md @@ -0,0 +1,132 @@ +# GitLab MCP Server + +MCP Server for the GitLab API, enabling project management, file operations, and more. + +### Features + +- **Automatic Branch Creation**: When creating/updating files or pushing changes, branches are automatically created if they don't exist +- **Comprehensive Error Handling**: Clear error messages for common issues +- **Git History Preservation**: Operations maintain proper Git history without force pushing +- **Batch Operations**: Support for both single-file and multi-file operations + + +## Tools + +1. `create_or_update_file` + - Create or update a single file in a project + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `file_path` (string): Path where to create/update the file + - `content` (string): Content of the file + - `commit_message` (string): Commit message + - `branch` (string): Branch to create/update the file in + - `previous_path` (optional string): Path of the file to move/rename + - Returns: File content and commit details + +2. `push_files` + - Push multiple files in a single commit + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `branch` (string): Branch to push to + - `files` (array): Files to push, each with `file_path` and `content` + - `commit_message` (string): Commit message + - Returns: Updated branch reference + +3. `search_repositories` + - Search for GitLab projects + - Inputs: + - `search` (string): Search query + - `page` (optional number): Page number for pagination + - `per_page` (optional number): Results per page (default 20) + - Returns: Project search results + +4. `create_repository` + - Create a new GitLab project + - Inputs: + - `name` (string): Project name + - `description` (optional string): Project description + - `visibility` (optional string): 'private', 'internal', or 'public' + - `initialize_with_readme` (optional boolean): Initialize with README + - Returns: Created project details + +5. `get_file_contents` + - Get contents of a file or directory + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `file_path` (string): Path to file/directory + - `ref` (optional string): Branch/tag/commit to get contents from + - Returns: File/directory contents + +6. `create_issue` + - Create a new issue + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `title` (string): Issue title + - `description` (optional string): Issue description + - `assignee_ids` (optional number[]): User IDs to assign + - `labels` (optional string[]): Labels to add + - `milestone_id` (optional number): Milestone ID + - Returns: Created issue details + +7. `create_merge_request` + - Create a new merge request + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `title` (string): MR title + - `description` (optional string): MR description + - `source_branch` (string): Branch containing changes + - `target_branch` (string): Branch to merge into + - `draft` (optional boolean): Create as draft MR + - `allow_collaboration` (optional boolean): Allow commits from upstream members + - Returns: Created merge request details + +8. `fork_repository` + - Fork a project + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `namespace` (optional string): Namespace to fork to + - Returns: Forked project details + +9. `create_branch` + - Create a new branch + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `branch` (string): Name for new branch + - `ref` (optional string): Source branch/commit for new branch + - Returns: Created branch reference + +## Setup + +### Personal Access Token +[Create a GitLab Personal Access Token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) with appropriate permissions: + - Go to User Settings > Access Tokens in GitLab + - Select the required scopes: + - `api` for full API access + - `read_api` for read-only access + - `read_repository` and `write_repository` for repository operations + - Create the token and save it securely + +### Usage with Claude Desktop +Add the following to your `claude_desktop_config.json`: + +```json +{ + "gitlab": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-gitlab"], + "env": { + "GITLAB_PERSONAL_ACCESS_TOKEN": "", + "GITLAB_API_URL": "https://gitlab.com/api/v4" // Optional, for self-hosted instances + } + } +} +``` + +## Environment Variables + +- `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token (required) +- `GITLAB_API_URL`: Base URL for GitLab API (optional, defaults to `https://gitlab.com/api/v4`) + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. \ No newline at end of file diff --git a/src/gitlab/index.ts b/src/gitlab/index.ts new file mode 100644 index 0000000..e246af4 --- /dev/null +++ b/src/gitlab/index.ts @@ -0,0 +1,534 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import fetch from "node-fetch"; +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; +import { + GitLabForkSchema, + GitLabReferenceSchema, + GitLabRepositorySchema, + GitLabIssueSchema, + GitLabMergeRequestSchema, + GitLabContentSchema, + GitLabCreateUpdateFileResponseSchema, + GitLabSearchResponseSchema, + GitLabTreeSchema, + GitLabCommitSchema, + CreateRepositoryOptionsSchema, + CreateIssueOptionsSchema, + CreateMergeRequestOptionsSchema, + CreateBranchOptionsSchema, + CreateOrUpdateFileSchema, + SearchRepositoriesSchema, + CreateRepositorySchema, + GetFileContentsSchema, + PushFilesSchema, + CreateIssueSchema, + CreateMergeRequestSchema, + ForkRepositorySchema, + CreateBranchSchema, + type GitLabFork, + type GitLabReference, + type GitLabRepository, + type GitLabIssue, + type GitLabMergeRequest, + type GitLabContent, + type GitLabCreateUpdateFileResponse, + type GitLabSearchResponse, + type GitLabTree, + type GitLabCommit, + type FileOperation, +} from './schemas.js'; + +const server = new Server({ + name: "gitlab-mcp-server", + version: "0.5.1", +}, { + capabilities: { + tools: {} + } +}); + +const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN; +const GITLAB_API_URL = process.env.GITLAB_API_URL || 'https://gitlab.com/api/v4'; + +if (!GITLAB_PERSONAL_ACCESS_TOKEN) { + console.error("GITLAB_PERSONAL_ACCESS_TOKEN environment variable is not set"); + process.exit(1); +} + +async function forkProject( + projectId: string, + namespace?: string +): Promise { + const url = `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork`; + const queryParams = namespace ? `?namespace=${encodeURIComponent(namespace)}` : ''; + + const response = await fetch(url + queryParams, { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + } + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabForkSchema.parse(await response.json()); +} + +async function createBranch( + projectId: string, + options: z.infer +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/branches`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + branch: options.name, + ref: options.ref + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabReferenceSchema.parse(await response.json()); +} + +async function getDefaultBranchRef(projectId: string): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`, + { + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}` + } + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + const project = GitLabRepositorySchema.parse(await response.json()); + return project.default_branch; +} + +async function getFileContents( + projectId: string, + filePath: string, + ref?: string +): Promise { + const encodedPath = encodeURIComponent(filePath); + let url = `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`; + if (ref) { + url += `?ref=${encodeURIComponent(ref)}`; + } + + const response = await fetch(url, { + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}` + } + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + const data = GitLabContentSchema.parse(await response.json()); + + if (!Array.isArray(data) && data.content) { + data.content = Buffer.from(data.content, 'base64').toString('utf8'); + } + + return data; +} + +async function createIssue( + projectId: string, + options: z.infer +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + title: options.title, + description: options.description, + assignee_ids: options.assignee_ids, + milestone_id: options.milestone_id, + labels: options.labels?.join(',') + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabIssueSchema.parse(await response.json()); +} + +async function createMergeRequest( + projectId: string, + options: z.infer +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + title: options.title, + description: options.description, + source_branch: options.source_branch, + target_branch: options.target_branch, + allow_collaboration: options.allow_collaboration, + draft: options.draft + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabMergeRequestSchema.parse(await response.json()); +} + +async function createOrUpdateFile( + projectId: string, + filePath: string, + content: string, + commitMessage: string, + branch: string, + previousPath?: string +): Promise { + const encodedPath = encodeURIComponent(filePath); + const url = `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`; + + const body = { + branch, + content, + commit_message: commitMessage, + ...(previousPath ? { previous_path: previousPath } : {}) + }; + + // Check if file exists + let method = "POST"; + try { + await getFileContents(projectId, filePath, branch); + method = "PUT"; + } catch (error) { + // File doesn't exist, use POST + } + + const response = await fetch(url, { + method, + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabCreateUpdateFileResponseSchema.parse(await response.json()); +} + +async function createTree( + projectId: string, + files: FileOperation[], + ref?: string +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/tree`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + files: files.map(file => ({ + file_path: file.path, + content: file.content + })), + ...(ref ? { ref } : {}) + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabTreeSchema.parse(await response.json()); +} + +async function createCommit( + projectId: string, + message: string, + branch: string, + actions: FileOperation[] +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/commits`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + branch, + commit_message: message, + actions: actions.map(action => ({ + action: "create", + file_path: action.path, + content: action.content + })) + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabCommitSchema.parse(await response.json()); +} + +async function searchProjects( + query: string, + page: number = 1, + perPage: number = 20 +): Promise { + const url = new URL(`${GITLAB_API_URL}/projects`); + url.searchParams.append("search", query); + url.searchParams.append("page", page.toString()); + url.searchParams.append("per_page", perPage.toString()); + + const response = await fetch(url.toString(), { + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}` + } + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + const projects = await response.json(); + return GitLabSearchResponseSchema.parse({ + count: parseInt(response.headers.get("X-Total") || "0"), + items: projects + }); +} + +async function createRepository( + options: z.infer +): Promise { + const response = await fetch(`${GITLAB_API_URL}/projects`, { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + name: options.name, + description: options.description, + visibility: options.visibility, + initialize_with_readme: options.initialize_with_readme + }) + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabRepositorySchema.parse(await response.json()); +} + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "create_or_update_file", + description: "Create or update a single file in a GitLab project", + inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema) + }, + { + name: "search_repositories", + description: "Search for GitLab projects", + inputSchema: zodToJsonSchema(SearchRepositoriesSchema) + }, + { + name: "create_repository", + description: "Create a new GitLab project", + inputSchema: zodToJsonSchema(CreateRepositorySchema) + }, + { + name: "get_file_contents", + description: "Get the contents of a file or directory from a GitLab project", + inputSchema: zodToJsonSchema(GetFileContentsSchema) + }, + { + name: "push_files", + description: "Push multiple files to a GitLab project in a single commit", + inputSchema: zodToJsonSchema(PushFilesSchema) + }, + { + name: "create_issue", + description: "Create a new issue in a GitLab project", + inputSchema: zodToJsonSchema(CreateIssueSchema) + }, + { + name: "create_merge_request", + description: "Create a new merge request in a GitLab project", + inputSchema: zodToJsonSchema(CreateMergeRequestSchema) + }, + { + name: "fork_repository", + description: "Fork a GitLab project to your account or specified namespace", + inputSchema: zodToJsonSchema(ForkRepositorySchema) + }, + { + name: "create_branch", + description: "Create a new branch in a GitLab project", + inputSchema: zodToJsonSchema(CreateBranchSchema) + } + ] + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + try { + if (!request.params.arguments) { + throw new Error("Arguments are required"); + } + + switch (request.params.name) { + case "fork_repository": { + const args = ForkRepositorySchema.parse(request.params.arguments); + const fork = await forkProject(args.project_id, args.namespace); + return { toolResult: fork }; + } + + case "create_branch": { + const args = CreateBranchSchema.parse(request.params.arguments); + let ref = args.ref; + if (!ref) { + ref = await getDefaultBranchRef(args.project_id); + } + + const branch = await createBranch(args.project_id, { + name: args.branch, + ref + }); + + return { toolResult: branch }; + } + + case "search_repositories": { + const args = SearchRepositoriesSchema.parse(request.params.arguments); + const results = await searchProjects(args.search, args.page, args.per_page); + return { toolResult: results }; + } + + case "create_repository": { + const args = CreateRepositorySchema.parse(request.params.arguments); + const repository = await createRepository(args); + return { toolResult: repository }; + } + + case "get_file_contents": { + const args = GetFileContentsSchema.parse(request.params.arguments); + const contents = await getFileContents(args.project_id, args.file_path, args.ref); + return { toolResult: contents }; + } + + case "create_or_update_file": { + const args = CreateOrUpdateFileSchema.parse(request.params.arguments); + const result = await createOrUpdateFile( + args.project_id, + args.file_path, + args.content, + args.commit_message, + args.branch, + args.previous_path + ); + return { toolResult: result }; + } + + case "push_files": { + const args = PushFilesSchema.parse(request.params.arguments); + const result = await createCommit( + args.project_id, + args.commit_message, + args.branch, + args.files.map(f => ({ path: f.file_path, content: f.content })) + ); + return { toolResult: result }; + } + + case "create_issue": { + const args = CreateIssueSchema.parse(request.params.arguments); + const { project_id, ...options } = args; + const issue = await createIssue(project_id, options); + return { toolResult: issue }; + } + + case "create_merge_request": { + const args = CreateMergeRequestSchema.parse(request.params.arguments); + const { project_id, ...options } = args; + const mergeRequest = await createMergeRequest(project_id, options); + return { toolResult: mergeRequest }; + } + + default: + throw new Error(`Unknown tool: ${request.params.name}`); + } + } catch (error) { + if (error instanceof z.ZodError) { + throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`); + } + throw error; + } +}); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("GitLab MCP Server running on stdio"); +} + +runServer().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); +}); \ No newline at end of file diff --git a/src/gitlab/package-lock.json b/src/gitlab/package-lock.json new file mode 100644 index 0000000..9cb2869 --- /dev/null +++ b/src/gitlab/package-lock.json @@ -0,0 +1,551 @@ +{ + "name": "@modelcontextprotocol/server-gitlab", + "version": "0.5.1", + "lockfileVersion": 1, + "requires": true, + "packages": { + "": { + "name": "@modelcontextprotocol/server-gitlab", + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2" + }, + "bin": { + "mcp-server-gitlab": "dist/index.js" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", + "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@types/node": { + "version": "22.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", + "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/src/gitlab/package.json b/src/gitlab/package.json new file mode 100644 index 0000000..e4b35ff --- /dev/null +++ b/src/gitlab/package.json @@ -0,0 +1,31 @@ +{ + "name": "@modelcontextprotocol/server-gitlab", + "version": "0.5.1", + "description": "MCP server for using the GitLab API", + "license": "MIT", + "author": "GitLab, PBC (https://gitlab.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-gitlab": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2", + "zod-to-json-schema": "^3.23.5" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/gitlab/schemas.ts b/src/gitlab/schemas.ts new file mode 100644 index 0000000..108c190 --- /dev/null +++ b/src/gitlab/schemas.ts @@ -0,0 +1,325 @@ +import { z } from 'zod'; + +// Base schemas for common types +export const GitLabAuthorSchema = z.object({ + name: z.string(), + email: z.string(), + date: z.string() +}); + +// Repository related schemas +export const GitLabOwnerSchema = z.object({ + username: z.string(), // Changed from login to match GitLab API + id: z.number(), + avatar_url: z.string(), + web_url: z.string(), // Changed from html_url to match GitLab API + name: z.string(), // Added as GitLab includes full name + state: z.string() // Added as GitLab includes user state +}); + +export const GitLabRepositorySchema = z.object({ + id: z.number(), + name: z.string(), + path_with_namespace: z.string(), // Changed from full_name to match GitLab API + visibility: z.string(), // Changed from private to match GitLab API + owner: GitLabOwnerSchema, + web_url: z.string(), // Changed from html_url to match GitLab API + description: z.string().nullable(), + fork: z.boolean(), + ssh_url_to_repo: z.string(), // Changed from ssh_url to match GitLab API + http_url_to_repo: z.string(), // Changed from clone_url to match GitLab API + created_at: z.string(), + last_activity_at: z.string(), // Changed from updated_at to match GitLab API + default_branch: z.string() +}); + +// File content schemas +export const GitLabFileContentSchema = z.object({ + file_name: z.string(), // Changed from name to match GitLab API + file_path: z.string(), // Changed from path to match GitLab API + size: z.number(), + encoding: z.string(), + content: z.string(), + content_sha256: z.string(), // Changed from sha to match GitLab API + ref: z.string(), // Added as GitLab requires branch reference + blob_id: z.string(), // Added to match GitLab API + last_commit_id: z.string() // Added to match GitLab API +}); + +export const GitLabDirectoryContentSchema = z.object({ + name: z.string(), + path: z.string(), + type: z.string(), + mode: z.string(), + id: z.string(), // Changed from sha to match GitLab API + web_url: z.string() // Changed from html_url to match GitLab API +}); + +export const GitLabContentSchema = z.union([ + GitLabFileContentSchema, + z.array(GitLabDirectoryContentSchema) +]); + +// Operation schemas +export const FileOperationSchema = z.object({ + path: z.string(), + content: z.string() +}); + +// Tree and commit schemas +export const GitLabTreeEntrySchema = z.object({ + id: z.string(), // Changed from sha to match GitLab API + name: z.string(), + type: z.enum(['blob', 'tree']), + path: z.string(), + mode: z.string() +}); + +export const GitLabTreeSchema = z.object({ + id: z.string(), // Changed from sha to match GitLab API + tree: z.array(GitLabTreeEntrySchema) +}); + +export const GitLabCommitSchema = z.object({ + id: z.string(), // Changed from sha to match GitLab API + short_id: z.string(), // Added to match GitLab API + title: z.string(), // Changed from message to match GitLab API + author_name: z.string(), + author_email: z.string(), + authored_date: z.string(), + committer_name: z.string(), + committer_email: z.string(), + committed_date: z.string(), + web_url: z.string(), // Changed from html_url to match GitLab API + parent_ids: z.array(z.string()) // Changed from parents to match GitLab API +}); + +// Reference schema +export const GitLabReferenceSchema = z.object({ + name: z.string(), // Changed from ref to match GitLab API + commit: z.object({ + id: z.string(), // Changed from sha to match GitLab API + web_url: z.string() // Changed from url to match GitLab API + }) +}); + +// Input schemas for operations +export const CreateRepositoryOptionsSchema = z.object({ + name: z.string(), + description: z.string().optional(), + visibility: z.enum(['private', 'internal', 'public']).optional(), // Changed from private to match GitLab API + initialize_with_readme: z.boolean().optional() // Changed from auto_init to match GitLab API +}); + +export const CreateIssueOptionsSchema = z.object({ + title: z.string(), + description: z.string().optional(), // Changed from body to match GitLab API + assignee_ids: z.array(z.number()).optional(), // Changed from assignees to match GitLab API + milestone_id: z.number().optional(), // Changed from milestone to match GitLab API + labels: z.array(z.string()).optional() +}); + +export const CreateMergeRequestOptionsSchema = z.object({ // Changed from CreatePullRequestOptionsSchema + title: z.string(), + description: z.string().optional(), // Changed from body to match GitLab API + source_branch: z.string(), // Changed from head to match GitLab API + target_branch: z.string(), // Changed from base to match GitLab API + allow_collaboration: z.boolean().optional(), // Changed from maintainer_can_modify to match GitLab API + draft: z.boolean().optional() +}); + +export const CreateBranchOptionsSchema = z.object({ + name: z.string(), // Changed from ref to match GitLab API + ref: z.string() // The source branch/commit for the new branch +}); + +// Response schemas for operations +export const GitLabCreateUpdateFileResponseSchema = z.object({ + file_path: z.string(), + branch: z.string(), + commit_id: z.string(), // Changed from sha to match GitLab API + content: GitLabFileContentSchema.optional() +}); + +export const GitLabSearchResponseSchema = z.object({ + count: z.number(), // Changed from total_count to match GitLab API + items: z.array(GitLabRepositorySchema) +}); + +// Fork related schemas +export const GitLabForkParentSchema = z.object({ + name: z.string(), + path_with_namespace: z.string(), // Changed from full_name to match GitLab API + owner: z.object({ + username: z.string(), // Changed from login to match GitLab API + id: z.number(), + avatar_url: z.string() + }), + web_url: z.string() // Changed from html_url to match GitLab API +}); + +export const GitLabForkSchema = GitLabRepositorySchema.extend({ + forked_from_project: GitLabForkParentSchema // Changed from parent to match GitLab API +}); + +// Issue related schemas +export const GitLabLabelSchema = z.object({ + id: z.number(), + name: z.string(), + color: z.string(), + description: z.string().optional() +}); + +export const GitLabUserSchema = z.object({ + username: z.string(), // Changed from login to match GitLab API + id: z.number(), + name: z.string(), + avatar_url: z.string(), + web_url: z.string() // Changed from html_url to match GitLab API +}); + +export const GitLabMilestoneSchema = z.object({ + id: z.number(), + iid: z.number(), // Added to match GitLab API + title: z.string(), + description: z.string(), + state: z.string(), + web_url: z.string() // Changed from html_url to match GitLab API +}); + +export const GitLabIssueSchema = z.object({ + id: z.number(), + iid: z.number(), // Added to match GitLab API + project_id: z.number(), // Added to match GitLab API + title: z.string(), + description: z.string(), // Changed from body to match GitLab API + state: z.string(), + author: GitLabUserSchema, + assignees: z.array(GitLabUserSchema), + labels: z.array(GitLabLabelSchema), + milestone: GitLabMilestoneSchema.nullable(), + created_at: z.string(), + updated_at: z.string(), + closed_at: z.string().nullable(), + web_url: z.string() // Changed from html_url to match GitLab API +}); + +// Merge Request related schemas (equivalent to Pull Request) +export const GitLabMergeRequestDiffRefSchema = z.object({ + base_sha: z.string(), + head_sha: z.string(), + start_sha: z.string() +}); + +export const GitLabMergeRequestSchema = z.object({ + id: z.number(), + iid: z.number(), // Added to match GitLab API + project_id: z.number(), // Added to match GitLab API + title: z.string(), + description: z.string(), // Changed from body to match GitLab API + state: z.string(), + merged: z.boolean(), + author: GitLabUserSchema, + assignees: z.array(GitLabUserSchema), + source_branch: z.string(), // Changed from head to match GitLab API + target_branch: z.string(), // Changed from base to match GitLab API + diff_refs: GitLabMergeRequestDiffRefSchema, + web_url: z.string(), // Changed from html_url to match GitLab API + created_at: z.string(), + updated_at: z.string(), + merged_at: z.string().nullable(), + closed_at: z.string().nullable(), + merge_commit_sha: z.string().nullable() +}); + +// API Operation Parameter Schemas +const ProjectParamsSchema = z.object({ + project_id: z.string().describe("Project ID or URL-encoded path") // Changed from owner/repo to match GitLab API +}); + +export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({ + file_path: z.string().describe("Path where to create/update the file"), + content: z.string().describe("Content of the file"), + commit_message: z.string().describe("Commit message"), + branch: z.string().describe("Branch to create/update the file in"), + previous_path: z.string().optional() + .describe("Path of the file to move/rename") +}); + +export const SearchRepositoriesSchema = z.object({ + search: z.string().describe("Search query"), // Changed from query to match GitLab API + page: z.number().optional().describe("Page number for pagination (default: 1)"), + per_page: z.number().optional().describe("Number of results per page (default: 20)") +}); + +export const CreateRepositorySchema = z.object({ + name: z.string().describe("Repository name"), + description: z.string().optional().describe("Repository description"), + visibility: z.enum(['private', 'internal', 'public']).optional() + .describe("Repository visibility level"), + initialize_with_readme: z.boolean().optional() + .describe("Initialize with README.md") +}); + +export const GetFileContentsSchema = ProjectParamsSchema.extend({ + file_path: z.string().describe("Path to the file or directory"), + ref: z.string().optional().describe("Branch/tag/commit to get contents from") +}); + +export const PushFilesSchema = ProjectParamsSchema.extend({ + branch: z.string().describe("Branch to push to"), + files: z.array(z.object({ + file_path: z.string().describe("Path where to create the file"), + content: z.string().describe("Content of the file") + })).describe("Array of files to push"), + commit_message: z.string().describe("Commit message") +}); + +export const CreateIssueSchema = ProjectParamsSchema.extend({ + title: z.string().describe("Issue title"), + description: z.string().optional().describe("Issue description"), + assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign"), + labels: z.array(z.string()).optional().describe("Array of label names"), + milestone_id: z.number().optional().describe("Milestone ID to assign") +}); + +export const CreateMergeRequestSchema = ProjectParamsSchema.extend({ + title: z.string().describe("Merge request title"), + description: z.string().optional().describe("Merge request description"), + source_branch: z.string().describe("Branch containing changes"), + target_branch: z.string().describe("Branch to merge into"), + draft: z.boolean().optional().describe("Create as draft merge request"), + allow_collaboration: z.boolean().optional() + .describe("Allow commits from upstream members") +}); + +export const ForkRepositorySchema = ProjectParamsSchema.extend({ + namespace: z.string().optional() + .describe("Namespace to fork to (full path)") +}); + +export const CreateBranchSchema = ProjectParamsSchema.extend({ + branch: z.string().describe("Name for the new branch"), + ref: z.string().optional() + .describe("Source branch/commit for new branch") +}); + +// Export types +export type GitLabAuthor = z.infer; +export type GitLabFork = z.infer; +export type GitLabIssue = z.infer; +export type GitLabMergeRequest = z.infer; +export type GitLabRepository = z.infer; +export type GitLabFileContent = z.infer; +export type GitLabDirectoryContent = z.infer; +export type GitLabContent = z.infer; +export type FileOperation = z.infer; +export type GitLabTree = z.infer; +export type GitLabCommit = z.infer; +export type GitLabReference = z.infer; +export type CreateRepositoryOptions = z.infer; +export type CreateIssueOptions = z.infer; +export type CreateMergeRequestOptions = z.infer; +export type CreateBranchOptions = z.infer; +export type GitLabCreateUpdateFileResponse = z.infer; +export type GitLabSearchResponse = z.infer; \ No newline at end of file diff --git a/src/gitlab/tsconfig.json b/src/gitlab/tsconfig.json new file mode 100644 index 0000000..4d33cae --- /dev/null +++ b/src/gitlab/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] + } + \ No newline at end of file diff --git a/src/google-maps/README.md b/src/google-maps/README.md new file mode 100644 index 0000000..962f031 --- /dev/null +++ b/src/google-maps/README.md @@ -0,0 +1,81 @@ +# Google Maps MCP Server + +MCP Server for the Google Maps API. + +## Tools + +1. `geocode` + - Convert address to coordinates + - Input: `address` (string) + - Returns: location, formatted_address, place_id + +2. `reverse_geocode` + - Convert coordinates to address + - Inputs: + - `latitude` (number) + - `longitude` (number) + - Returns: formatted_address, place_id, address_components + +3. `search_places` + - Search for places using text query + - Inputs: + - `query` (string) + - `location` (optional): { latitude: number, longitude: number } + - `radius` (optional): number (meters, max 50000) + - Returns: array of places with names, addresses, locations + +4. `get_place_details` + - Get detailed information about a place + - Input: `place_id` (string) + - Returns: name, address, contact info, ratings, reviews, opening hours + +5. `get_distance_matrix` + - Calculate distances and times between points + - Inputs: + - `origins` (string[]) + - `destinations` (string[]) + - `mode` (optional): "driving" | "walking" | "bicycling" | "transit" + - Returns: distances and durations matrix + +6. `get_elevation` + - Get elevation data for locations + - Input: `locations` (array of {latitude, longitude}) + - Returns: elevation data for each point + +7. `get_directions` + - Get directions between points + - Inputs: + - `origin` (string) + - `destination` (string) + - `mode` (optional): "driving" | "walking" | "bicycling" | "transit" + - Returns: route details with steps, distance, duration + +## Setup + +### API Key +Get a Google Maps API key by following the instructions [here](https://developers.google.com/maps/documentation/javascript/get-api-key#create-api-keys). + +### Usage with Claude Desktop + +Add the following to your `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "google-maps": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-google-maps" + ], + "env": { + "GOOGLE_MAPS_API_KEY": "" + } + } + } +} +``` + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/google-maps/index.ts b/src/google-maps/index.ts new file mode 100644 index 0000000..937b39a --- /dev/null +++ b/src/google-maps/index.ts @@ -0,0 +1,710 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + Tool, +} from "@modelcontextprotocol/sdk/types.js"; +import fetch from "node-fetch"; + +// Response interfaces +interface GoogleMapsResponse { + status: string; + error_message?: string; +} + +interface GeocodeResponse extends GoogleMapsResponse { + results: Array<{ + place_id: string; + formatted_address: string; + geometry: { + location: { + lat: number; + lng: number; + } + }; + address_components: Array<{ + long_name: string; + short_name: string; + types: string[]; + }>; + }>; +} + +interface PlacesSearchResponse extends GoogleMapsResponse { + results: Array<{ + name: string; + place_id: string; + formatted_address: string; + geometry: { + location: { + lat: number; + lng: number; + } + }; + rating?: number; + types: string[]; + }>; +} + +interface PlaceDetailsResponse extends GoogleMapsResponse { + result: { + name: string; + place_id: string; + formatted_address: string; + formatted_phone_number?: string; + website?: string; + rating?: number; + reviews?: Array<{ + author_name: string; + rating: number; + text: string; + time: number; + }>; + opening_hours?: { + weekday_text: string[]; + open_now: boolean; + }; + geometry: { + location: { + lat: number; + lng: number; + } + }; + }; +} + +interface DistanceMatrixResponse extends GoogleMapsResponse { + origin_addresses: string[]; + destination_addresses: string[]; + rows: Array<{ + elements: Array<{ + status: string; + duration: { + text: string; + value: number; + }; + distance: { + text: string; + value: number; + }; + }>; + }>; +} + +interface ElevationResponse extends GoogleMapsResponse { + results: Array<{ + elevation: number; + location: { + lat: number; + lng: number; + }; + resolution: number; + }>; +} + +interface DirectionsResponse extends GoogleMapsResponse { + routes: Array<{ + summary: string; + legs: Array<{ + distance: { + text: string; + value: number; + }; + duration: { + text: string; + value: number; + }; + steps: Array<{ + html_instructions: string; + distance: { + text: string; + value: number; + }; + duration: { + text: string; + value: number; + }; + travel_mode: string; + }>; + }>; + }>; +} + +function getApiKey(): string { + const apiKey = process.env.GOOGLE_MAPS_API_KEY; + if (!apiKey) { + console.error("GOOGLE_MAPS_API_KEY environment variable is not set"); + process.exit(1); + } + return apiKey; + } + +const GOOGLE_MAPS_API_KEY = getApiKey(); + +// Tool definitions +const GEOCODE_TOOL: Tool = { + name: "maps_geocode", + description: "Convert an address into geographic coordinates", + inputSchema: { + type: "object", + properties: { + address: { + type: "string", + description: "The address to geocode" + } + }, + required: ["address"] + } + }; + +const REVERSE_GEOCODE_TOOL: Tool = { + name: "maps_reverse_geocode", + description: "Convert coordinates into an address", + inputSchema: { + type: "object", + properties: { + latitude: { + type: "number", + description: "Latitude coordinate" + }, + longitude: { + type: "number", + description: "Longitude coordinate" + } + }, + required: ["latitude", "longitude"] + } +}; + +const SEARCH_PLACES_TOOL: Tool = { + name: "maps_search_places", + description: "Search for places using Google Places API", + inputSchema: { + type: "object", + properties: { + query: { + type: "string", + description: "Search query" + }, + location: { + type: "object", + properties: { + latitude: { type: "number" }, + longitude: { type: "number" } + }, + description: "Optional center point for the search" + }, + radius: { + type: "number", + description: "Search radius in meters (max 50000)" + } + }, + required: ["query"] + } +}; + +const PLACE_DETAILS_TOOL: Tool = { + name: "maps_place_details", + description: "Get detailed information about a specific place", + inputSchema: { + type: "object", + properties: { + place_id: { + type: "string", + description: "The place ID to get details for" + } + }, + required: ["place_id"] + } +}; + +const DISTANCE_MATRIX_TOOL: Tool = { + name: "maps_distance_matrix", + description: "Calculate travel distance and time for multiple origins and destinations", + inputSchema: { + type: "object", + properties: { + origins: { + type: "array", + items: { type: "string" }, + description: "Array of origin addresses or coordinates" + }, + destinations: { + type: "array", + items: { type: "string" }, + description: "Array of destination addresses or coordinates" + }, + mode: { + type: "string", + description: "Travel mode (driving, walking, bicycling, transit)", + enum: ["driving", "walking", "bicycling", "transit"] + } + }, + required: ["origins", "destinations"] + } +}; + +const ELEVATION_TOOL: Tool = { + name: "maps_elevation", + description: "Get elevation data for locations on the earth", + inputSchema: { + type: "object", + properties: { + locations: { + type: "array", + items: { + type: "object", + properties: { + latitude: { type: "number" }, + longitude: { type: "number" } + }, + required: ["latitude", "longitude"] + }, + description: "Array of locations to get elevation for" + } + }, + required: ["locations"] + } +}; + +const DIRECTIONS_TOOL: Tool = { + name: "maps_directions", + description: "Get directions between two points", + inputSchema: { + type: "object", + properties: { + origin: { + type: "string", + description: "Starting point address or coordinates" + }, + destination: { + type: "string", + description: "Ending point address or coordinates" + }, + mode: { + type: "string", + description: "Travel mode (driving, walking, bicycling, transit)", + enum: ["driving", "walking", "bicycling", "transit"] + } + }, + required: ["origin", "destination"] + } +}; + +const MAPS_TOOLS = [ + GEOCODE_TOOL, + REVERSE_GEOCODE_TOOL, + SEARCH_PLACES_TOOL, + PLACE_DETAILS_TOOL, + DISTANCE_MATRIX_TOOL, + ELEVATION_TOOL, + DIRECTIONS_TOOL, +] as const; + +// API handlers +async function handleGeocode(address: string) { + const url = new URL("https://maps.googleapis.com/maps/api/geocode/json"); + url.searchParams.append("address", address); + url.searchParams.append("key", GOOGLE_MAPS_API_KEY); + + const response = await fetch(url.toString()); + const data = await response.json() as GeocodeResponse; + + if (data.status !== "OK") { + return { + toolResult: { + content: [{ + type: "text", + text: `Geocoding failed: ${data.error_message || data.status}` + }], + isError: true + } + }; + } + + return { + toolResult: { + content: [{ + type: "text", + text: JSON.stringify({ + location: data.results[0].geometry.location, + formatted_address: data.results[0].formatted_address, + place_id: data.results[0].place_id + }, null, 2) + }], + isError: false + } + }; +} + +async function handleReverseGeocode(latitude: number, longitude: number) { + const url = new URL("https://maps.googleapis.com/maps/api/geocode/json"); + url.searchParams.append("latlng", `${latitude},${longitude}`); + url.searchParams.append("key", GOOGLE_MAPS_API_KEY); + + const response = await fetch(url.toString()); + const data = await response.json() as GeocodeResponse; + + if (data.status !== "OK") { + return { + toolResult: { + content: [{ + type: "text", + text: `Reverse geocoding failed: ${data.error_message || data.status}` + }], + isError: true + } + }; + } + + return { + toolResult: { + content: [{ + type: "text", + text: JSON.stringify({ + formatted_address: data.results[0].formatted_address, + place_id: data.results[0].place_id, + address_components: data.results[0].address_components + }, null, 2) + }], + isError: false + } + }; +} + +async function handlePlaceSearch( + query: string, + location?: { latitude: number; longitude: number }, + radius?: number +) { + const url = new URL("https://maps.googleapis.com/maps/api/place/textsearch/json"); + url.searchParams.append("query", query); + url.searchParams.append("key", GOOGLE_MAPS_API_KEY); + + if (location) { + url.searchParams.append("location", `${location.latitude},${location.longitude}`); + } + if (radius) { + url.searchParams.append("radius", radius.toString()); + } + + const response = await fetch(url.toString()); + const data = await response.json() as PlacesSearchResponse; + + if (data.status !== "OK") { + return { + toolResult: { + content: [{ + type: "text", + text: `Place search failed: ${data.error_message || data.status}` + }], + isError: true + } + }; + } + + return { + toolResult: { + content: [{ + type: "text", + text: JSON.stringify({ + places: data.results.map((place) => ({ + name: place.name, + formatted_address: place.formatted_address, + location: place.geometry.location, + place_id: place.place_id, + rating: place.rating, + types: place.types + })) + }, null, 2) + }], + isError: false + } + }; +} + +async function handlePlaceDetails(place_id: string) { + const url = new URL("https://maps.googleapis.com/maps/api/place/details/json"); + url.searchParams.append("place_id", place_id); + url.searchParams.append("key", GOOGLE_MAPS_API_KEY); + + const response = await fetch(url.toString()); + const data = await response.json() as PlaceDetailsResponse; + + if (data.status !== "OK") { + return { + toolResult: { + content: [{ + type: "text", + text: `Place details request failed: ${data.error_message || data.status}` + }], + isError: true + } + }; + } + + return { + toolResult: { + content: [{ + type: "text", + text: JSON.stringify({ + name: data.result.name, + formatted_address: data.result.formatted_address, + location: data.result.geometry.location, + formatted_phone_number: data.result.formatted_phone_number, + website: data.result.website, + rating: data.result.rating, + reviews: data.result.reviews, + opening_hours: data.result.opening_hours + }, null, 2) + }], + isError: false + } + }; +} +async function handleDistanceMatrix( + origins: string[], + destinations: string[], + mode: "driving" | "walking" | "bicycling" | "transit" = "driving" +) { + const url = new URL("https://maps.googleapis.com/maps/api/distancematrix/json"); + url.searchParams.append("origins", origins.join("|")); + url.searchParams.append("destinations", destinations.join("|")); + url.searchParams.append("mode", mode); + url.searchParams.append("key", GOOGLE_MAPS_API_KEY); + + const response = await fetch(url.toString()); + const data = await response.json() as DistanceMatrixResponse; + + if (data.status !== "OK") { + return { + toolResult: { + content: [{ + type: "text", + text: `Distance matrix request failed: ${data.error_message || data.status}` + }], + isError: true + } + }; + } + + return { + toolResult: { + content: [{ + type: "text", + text: JSON.stringify({ + origin_addresses: data.origin_addresses, + destination_addresses: data.destination_addresses, + results: data.rows.map((row) => ({ + elements: row.elements.map((element) => ({ + status: element.status, + duration: element.duration, + distance: element.distance + })) + })) + }, null, 2) + }], + isError: false + } + }; +} + +async function handleElevation(locations: Array<{ latitude: number; longitude: number }>) { + const url = new URL("https://maps.googleapis.com/maps/api/elevation/json"); + const locationString = locations + .map((loc) => `${loc.latitude},${loc.longitude}`) + .join("|"); + url.searchParams.append("locations", locationString); + url.searchParams.append("key", GOOGLE_MAPS_API_KEY); + + const response = await fetch(url.toString()); + const data = await response.json() as ElevationResponse; + + if (data.status !== "OK") { + return { + toolResult: { + content: [{ + type: "text", + text: `Elevation request failed: ${data.error_message || data.status}` + }], + isError: true + } + }; + } + + return { + toolResult: { + content: [{ + type: "text", + text: JSON.stringify({ + results: data.results.map((result) => ({ + elevation: result.elevation, + location: result.location, + resolution: result.resolution + })) + }, null, 2) + }], + isError: false + } + }; +} + +async function handleDirections( + origin: string, + destination: string, + mode: "driving" | "walking" | "bicycling" | "transit" = "driving" +) { + const url = new URL("https://maps.googleapis.com/maps/api/directions/json"); + url.searchParams.append("origin", origin); + url.searchParams.append("destination", destination); + url.searchParams.append("mode", mode); + url.searchParams.append("key", GOOGLE_MAPS_API_KEY); + + const response = await fetch(url.toString()); + const data = await response.json() as DirectionsResponse; + + if (data.status !== "OK") { + return { + toolResult: { + content: [{ + type: "text", + text: `Directions request failed: ${data.error_message || data.status}` + }], + isError: true + } + }; + } + + return { + toolResult: { + content: [{ + type: "text", + text: JSON.stringify({ + routes: data.routes.map((route) => ({ + summary: route.summary, + distance: route.legs[0].distance, + duration: route.legs[0].duration, + steps: route.legs[0].steps.map((step) => ({ + instructions: step.html_instructions, + distance: step.distance, + duration: step.duration, + travel_mode: step.travel_mode + })) + })) + }, null, 2) + }], + isError: false + } + }; +} + +// Server setup +const server = new Server( + { + name: "mcp-server/google-maps", + version: "0.1.0", + }, + { + capabilities: { + tools: {}, + }, + }, +); + +// Set up request handlers +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: MAPS_TOOLS, +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + try { + switch (request.params.name) { + case "maps_geocode": { + const { address } = request.params.arguments as { address: string }; + return await handleGeocode(address); + } + + case "maps_reverse_geocode": { + const { latitude, longitude } = request.params.arguments as { + latitude: number; + longitude: number; + }; + return await handleReverseGeocode(latitude, longitude); + } + + case "maps_search_places": { + const { query, location, radius } = request.params.arguments as { + query: string; + location?: { latitude: number; longitude: number }; + radius?: number; + }; + return await handlePlaceSearch(query, location, radius); + } + + case "maps_place_details": { + const { place_id } = request.params.arguments as { place_id: string }; + return await handlePlaceDetails(place_id); + } + + case "maps_distance_matrix": { + const { origins, destinations, mode } = request.params.arguments as { + origins: string[]; + destinations: string[]; + mode?: "driving" | "walking" | "bicycling" | "transit"; + }; + return await handleDistanceMatrix(origins, destinations, mode); + } + + case "maps_elevation": { + const { locations } = request.params.arguments as { + locations: Array<{ latitude: number; longitude: number }>; + }; + return await handleElevation(locations); + } + + case "maps_directions": { + const { origin, destination, mode } = request.params.arguments as { + origin: string; + destination: string; + mode?: "driving" | "walking" | "bicycling" | "transit"; + }; + return await handleDirections(origin, destination, mode); + } + + default: + return { + toolResult: { + content: [{ + type: "text", + text: `Unknown tool: ${request.params.name}` + }], + isError: true + } + }; + } + } catch (error) { + return { + toolResult: { + content: [{ + type: "text", + text: `Error: ${error instanceof Error ? error.message : String(error)}` + }], + isError: true + } + }; + } +}); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("Google Maps MCP Server running on stdio"); +} + +runServer().catch((error) => { + console.error("Fatal error running server:", error); + process.exit(1); +}); \ No newline at end of file diff --git a/src/google-maps/package.json b/src/google-maps/package.json new file mode 100644 index 0000000..dedf73c --- /dev/null +++ b/src/google-maps/package.json @@ -0,0 +1,30 @@ +{ + "name": "@modelcontextprotocol/server-google-maps", + "version": "0.5.1", + "description": "MCP server for using the Google Maps API", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-google-maps": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/google-maps/tsconfig.json b/src/google-maps/tsconfig.json new file mode 100644 index 0000000..ec5da15 --- /dev/null +++ b/src/google-maps/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] +} diff --git a/src/memory/Dockerfile b/src/memory/Dockerfile deleted file mode 100644 index 2f85d0c..0000000 --- a/src/memory/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM node:22.12-alpine AS builder - -COPY src/memory /app -COPY tsconfig.json /tsconfig.json - -WORKDIR /app - -RUN --mount=type=cache,target=/root/.npm npm install - -RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev - -FROM node:22-alpine AS release - -COPY --from=builder /app/dist /app/dist -COPY --from=builder /app/package.json /app/package.json -COPY --from=builder /app/package-lock.json /app/package-lock.json - -ENV NODE_ENV=production - -WORKDIR /app - -RUN npm ci --ignore-scripts --omit-dev - -ENTRYPOINT ["node", "dist/index.js"] \ No newline at end of file diff --git a/src/memory/README.md b/src/memory/README.md index de7be2c..66bdbb4 100644 --- a/src/memory/README.md +++ b/src/memory/README.md @@ -1,9 +1,6 @@ # Knowledge Graph Memory Server - A basic implementation of persistent memory using a local knowledge graph. This lets Claude remember information about the user across chats. -Published on npm as [`@modelcontextprotocol/server-memory`](https://www.npmjs.com/package/@modelcontextprotocol/server-memory). - ## Core Concepts ### Entities @@ -72,7 +69,6 @@ Example: - `to` (string): Target entity name - `relationType` (string): Relationship type in active voice - Skips duplicate relations - - Fails if either the source or target entity doesn't exist - **add_observations** - Add new observations to existing entities @@ -87,7 +83,7 @@ Example: - Remove entities and their relations - Input: `entityNames` (string[]) - Cascading deletion of associated relations - - No error if an entity doesn't exist; the response reports which names were not found + - Silent operation if entity doesn't exist - **delete_observations** - Remove specific observations from entities @@ -95,7 +91,7 @@ Example: - Each object contains: - `entityName` (string): Target entity - `observations` (string[]): Observations to remove - - No error if an observation doesn't exist; the response reports how many were deleted + - Silent operation if observation doesn't exist - **delete_relations** - Remove specific relations from the graph @@ -104,7 +100,7 @@ Example: - `from` (string): Source entity name - `to` (string): Target entity name - `relationType` (string): Relationship type - - No error if a relation doesn't exist; the response reports how many were deleted + - Silent operation if relation doesn't exist - **read_graph** - Read the entire knowledge graph @@ -128,34 +124,10 @@ Example: - Relations between requested entities - Silently skips non-existent nodes -### Resources - -- **knowledge-graph** (`memory://knowledge-graph`) - - The full knowledge graph as a readable MCP Resource - - MIME type: `application/json` - - Returns the same shape as `read_graph` (entities and relations) - - Mutation tools (`create_entities`, `create_relations`, `add_observations`, `delete_entities`, `delete_observations`, `delete_relations`) emit `notifications/resources/updated` for this URI, so subscribed clients see live changes - # Usage with Claude Desktop ### Setup - Add this to your claude_desktop_config.json: - -#### Docker - -```json -{ - "mcpServers": { - "memory": { - "command": "docker", - "args": ["run", "-i", "-v", "claude-memory:/app/dist", "--rm", "mcp/memory"] - } - } -} -``` - -#### NPX ```json { "mcpServers": { @@ -170,140 +142,6 @@ Add this to your claude_desktop_config.json: } ``` -On Windows, use `cmd /c` to launch `npx`: - -```json -{ - "mcpServers": { - "memory": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-memory" - ] - } - } -} -``` - -#### NPX with custom setting - -The server can be configured using the following environment variables: - -```json -{ - "mcpServers": { - "memory": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-memory" - ], - "env": { - "MEMORY_FILE_PATH": "/path/to/custom/memory.jsonl" - } - } - } -} -``` - -On Windows, use: - -```json -{ - "mcpServers": { - "memory": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-memory" - ], - "env": { - "MEMORY_FILE_PATH": "/path/to/custom/memory.jsonl" - } - } - } -} -``` - -- `MEMORY_FILE_PATH`: Path to the memory storage JSONL file (default: `memory.jsonl` in the server directory) - -# VS Code Installation Instructions - -For quick installation, use one of the one-click installation buttons below: - -[![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=memory&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-memory%22%5D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=memory&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-memory%22%5D%7D&quality=insiders) - -[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=memory&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22-v%22%2C%22claude-memory%3A%2Fapp%2Fdist%22%2C%22--rm%22%2C%22mcp%2Fmemory%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=memory&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22-v%22%2C%22claude-memory%3A%2Fapp%2Fdist%22%2C%22--rm%22%2C%22mcp%2Fmemory%22%5D%7D&quality=insiders) - -For manual installation, you can configure the MCP server using one of these methods: - -**Method 1: User Configuration (Recommended)** -Add the configuration to your user-level MCP configuration file. Open the Command Palette (`Ctrl + Shift + P`) and run `MCP: Open User Configuration`. This will open your user `mcp.json` file where you can add the server configuration. - -**Method 2: Workspace Configuration** -Alternatively, you can add the configuration to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. - -> For more details about MCP configuration in VS Code, see the [official VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers). - -#### NPX - -```json -{ - "servers": { - "memory": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-memory" - ] - } - } -} -``` - -On Windows, use: - -```json -{ - "servers": { - "memory": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-memory" - ] - } - } -} -``` - -#### Docker - -```json -{ - "servers": { - "memory": { - "command": "docker", - "args": [ - "run", - "-i", - "-v", - "claude-memory:/app/dist", - "--rm", - "mcp/memory" - ] - } - } -} -``` - ### System Prompt The prompt for utilizing memory depends on the use case. Changing the prompt will help the model determine the frequency and types of memories created. @@ -333,19 +171,9 @@ Follow these steps for each interaction: - If any new information was gathered during the interaction, update your memory as follows: a) Create entities for recurring organizations, people, and significant events b) Connect them to the current entities using relations - c) Store facts about them as observations + b) Store facts about them as observations ``` -## Building - -Docker: - -```sh -docker build -t mcp/memory -f src/memory/Dockerfile . -``` - -For Awareness: a prior mcp/memory volume contains an index.js file that could be overwritten by the new container. If you are using a docker volume for storage, delete the old docker volume's `index.js` file before starting the new container. - ## License This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/memory/__tests__/atomic-save.test.ts b/src/memory/__tests__/atomic-save.test.ts deleted file mode 100644 index 8c702b5..0000000 --- a/src/memory/__tests__/atomic-save.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import { promises as fs } from 'fs'; -import path from 'path'; -import os from 'os'; -import { KnowledgeGraphManager, Entity } from '../index.js'; - -/** - * Regression tests for durable persistence of the knowledge graph. - * - * saveGraph() previously called fs.writeFile() directly on the memory file. - * fs.writeFile opens the target with 'w', which truncates it before any new - * bytes are written. If the process dies between truncation and completion - * (SIGKILL, container stop, OOM kill, power loss) the memory file โ€” the sole - * persistence layer for the graph โ€” is left empty or half-written, and the - * accumulated memory is unrecoverable. - * - * The fix writes to a temporary file in the same directory and then renames - * it over the target. rename(2) is atomic on POSIX filesystems: a reader - * either sees the complete old file or the complete new one, never a - * truncated intermediate state. - */ -describe('KnowledgeGraphManager persistence durability', () => { - let testDir: string; - let testFilePath: string; - - beforeEach(async () => { - testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-memory-atomic-')); - testFilePath = path.join(testDir, 'memory.jsonl'); - }); - - afterEach(async () => { - vi.restoreAllMocks(); - await fs.rm(testDir, { recursive: true, force: true }); - }); - - it('never truncates the live memory file in place', async () => { - const manager = new KnowledgeGraphManager(testFilePath); - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - ]); - - // Any write that targets the live file directly is destructive: it - // truncates committed data before the replacement is durable. - const writeFileSpy = vi.spyOn(fs, 'writeFile'); - const openSpy = vi.spyOn(fs, 'open'); - - await manager.createEntities([ - { name: 'Bob', entityType: 'person', observations: ['likes programming'] }, - ]); - - const destructiveTargets = [ - ...writeFileSpy.mock.calls.map(call => call[0]), - ...openSpy.mock.calls.map(call => call[0]), - ].filter(target => target === testFilePath); - - expect(destructiveTargets).toEqual([]); - }); - - it('leaves the committed graph intact when the write fails midway', async () => { - const manager = new KnowledgeGraphManager(testFilePath); - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - ]); - - const before = await fs.readFile(testFilePath, 'utf-8'); - expect(before).toContain('Alice'); - - // Simulate the process being interrupted while persisting the next write. - vi.spyOn(fs, 'writeFile').mockImplementationOnce(async () => { - throw new Error('ENOSPC: simulated interruption'); - }); - - await expect( - manager.createEntities([ - { name: 'Bob', entityType: 'person', observations: ['likes programming'] }, - ]) - ).rejects.toThrow(); - - vi.restoreAllMocks(); - - // The previously committed graph must survive untouched. - expect(await fs.readFile(testFilePath, 'utf-8')).toBe(before); - - const graph = await new KnowledgeGraphManager(testFilePath).readGraph(); - expect(graph.entities.map(e => e.name)).toEqual(['Alice']); - }); - - it('does not leave temporary files behind after a successful write', async () => { - const manager = new KnowledgeGraphManager(testFilePath); - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - ]); - - expect(await fs.readdir(testDir)).toEqual(['memory.jsonl']); - }); - - it('cleans up the temporary file when the write fails', async () => { - const manager = new KnowledgeGraphManager(testFilePath); - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - ]); - - vi.spyOn(fs, 'rename').mockImplementationOnce(async () => { - throw new Error('EXDEV: simulated rename failure'); - }); - - await expect( - manager.createEntities([ - { name: 'Bob', entityType: 'person', observations: ['likes programming'] }, - ]) - ).rejects.toThrow(); - - vi.restoreAllMocks(); - expect(await fs.readdir(testDir)).toEqual(['memory.jsonl']); - }); - - it('still persists graph contents correctly across reloads', async () => { - const manager = new KnowledgeGraphManager(testFilePath); - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - { name: 'Bob', entityType: 'person', observations: ['likes programming'] }, - ]); - await manager.createRelations([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - ]); - - const reloaded = await new KnowledgeGraphManager(testFilePath).readGraph(); - expect(reloaded.entities.map(e => e.name).sort()).toEqual(['Alice', 'Bob']); - expect(reloaded.relations).toEqual([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - ]); - }); -}); diff --git a/src/memory/__tests__/delete-reporting.test.ts b/src/memory/__tests__/delete-reporting.test.ts deleted file mode 100644 index 944c302..0000000 --- a/src/memory/__tests__/delete-reporting.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { promises as fs } from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { KnowledgeGraphManager, Entity, Relation } from '../index.js'; - -/** - * The delete tools stay silent when a target is absent, which the README - * documents. What they must not do is report a deletion that did not happen: - * an agent that mistypes a name is told its memory is clean while the data is - * still on disk, and nothing in the response contradicts that. - */ -describe('delete reporting', () => { - let manager: KnowledgeGraphManager; - let testFilePath: string; - - const entities: Entity[] = [ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp', 'likes tea'] }, - { name: 'Bob', entityType: 'person', observations: ['likes programming'] }, - ]; - const relations: Relation[] = [{ from: 'Alice', to: 'Bob', relationType: 'works_with' }]; - - beforeEach(async () => { - testFilePath = path.join( - path.dirname(fileURLToPath(import.meta.url)), - `test-delete-reporting-${Date.now()}-${Math.random().toString(16).slice(2)}.jsonl` - ); - manager = new KnowledgeGraphManager(testFilePath); - await manager.createEntities(entities); - await manager.createRelations(relations); - }); - - afterEach(async () => { - try { - await fs.unlink(testFilePath); - } catch { - // the file is gone already - } - }); - - describe('deleteEntities', () => { - it('reports which names matched and which did not', async () => { - const result = await manager.deleteEntities(['Alice', 'Alise']); - expect(result).toEqual({ deleted: ['Alice'], notFound: ['Alise'] }); - }); - - it('reports nothing deleted when no name matches', async () => { - const result = await manager.deleteEntities(['Nobody']); - expect(result).toEqual({ deleted: [], notFound: ['Nobody'] }); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(2); - }); - - it('still deletes the entity and its relations', async () => { - await manager.deleteEntities(['Alice']); - - const graph = await manager.readGraph(); - expect(graph.entities.map(e => e.name)).toEqual(['Bob']); - expect(graph.relations).toHaveLength(0); - }); - }); - - describe('deleteObservations', () => { - it('counts only the observations that were present', async () => { - const result = await manager.deleteObservations([ - { entityName: 'Alice', observations: ['likes tea', 'never said this'] }, - ]); - expect(result).toEqual({ deletedCount: 1, missingEntities: [] }); - }); - - it('names an entity that does not exist', async () => { - const result = await manager.deleteObservations([ - { entityName: 'Carol', observations: ['anything'] }, - ]); - expect(result).toEqual({ deletedCount: 0, missingEntities: ['Carol'] }); - }); - }); - - describe('deleteRelations', () => { - it('counts only the relations that matched', async () => { - const result = await manager.deleteRelations([ - { from: 'Alice', to: 'Bob', relationType: 'works_with' }, - { from: 'Alice', to: 'Bob', relationType: 'never_existed' }, - ]); - expect(result).toEqual({ deletedCount: 1 }); - }); - - it('reports nothing deleted when the relation type is wrong', async () => { - const result = await manager.deleteRelations([ - { from: 'Alice', to: 'Bob', relationType: 'manages' }, - ]); - expect(result).toEqual({ deletedCount: 0 }); - - const graph = await manager.readGraph(); - expect(graph.relations).toHaveLength(1); - }); - }); -}); diff --git a/src/memory/__tests__/file-path.test.ts b/src/memory/__tests__/file-path.test.ts deleted file mode 100644 index fe5fdeb..0000000 --- a/src/memory/__tests__/file-path.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import { promises as fs } from 'fs'; -import path from 'path'; -import os from 'os'; -import { fileURLToPath } from 'url'; -import { ensureMemoryFilePath, defaultMemoryPath, expandHome } from '../index.js'; - -describe('ensureMemoryFilePath', () => { - const testDir = path.dirname(fileURLToPath(import.meta.url)); - const oldMemoryPath = path.join(testDir, '..', 'memory.json'); - const newMemoryPath = path.join(testDir, '..', 'memory.jsonl'); - - let originalEnv: string | undefined; - - beforeEach(() => { - // Save original environment variable - originalEnv = process.env.MEMORY_FILE_PATH; - // Delete environment variable - delete process.env.MEMORY_FILE_PATH; - }); - - afterEach(async () => { - // Restore original environment variable - if (originalEnv !== undefined) { - process.env.MEMORY_FILE_PATH = originalEnv; - } else { - delete process.env.MEMORY_FILE_PATH; - } - - // Clean up test files - try { - await fs.unlink(oldMemoryPath); - } catch { - // Ignore if file doesn't exist - } - try { - await fs.unlink(newMemoryPath); - } catch { - // Ignore if file doesn't exist - } - }); - - describe('with MEMORY_FILE_PATH environment variable', () => { - it('should return absolute path when MEMORY_FILE_PATH is absolute', async () => { - const absolutePath = '/tmp/custom-memory.jsonl'; - process.env.MEMORY_FILE_PATH = absolutePath; - - const result = await ensureMemoryFilePath(); - - expect(result).toBe(absolutePath); - }); - - it('should convert relative path to absolute when MEMORY_FILE_PATH is relative', async () => { - const relativePath = 'custom-memory.jsonl'; - process.env.MEMORY_FILE_PATH = relativePath; - - const result = await ensureMemoryFilePath(); - - expect(path.isAbsolute(result)).toBe(true); - expect(result).toContain('custom-memory.jsonl'); - }); - - it('should handle Windows absolute paths', async () => { - const windowsPath = 'C:\\temp\\memory.jsonl'; - process.env.MEMORY_FILE_PATH = windowsPath; - - const result = await ensureMemoryFilePath(); - - // On Windows, should return as-is; on Unix, will be treated as relative - if (process.platform === 'win32') { - expect(result).toBe(windowsPath); - } else { - expect(path.isAbsolute(result)).toBe(true); - } - }); - - it('should expand a leading "~/" to the home directory', async () => { - process.env.MEMORY_FILE_PATH = '~/custom-memory.jsonl'; - - const result = await ensureMemoryFilePath(); - - expect(result).toBe(path.join(os.homedir(), 'custom-memory.jsonl')); - expect(path.isAbsolute(result)).toBe(true); - }); - }); - - describe('without MEMORY_FILE_PATH environment variable', () => { - it('should return default path when no files exist', async () => { - const result = await ensureMemoryFilePath(); - - expect(result).toBe(defaultMemoryPath); - }); - - it('should migrate from memory.json to memory.jsonl when only old file exists', async () => { - // Create old memory.json file - await fs.writeFile(oldMemoryPath, '{"test":"data"}'); - - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - - const result = await ensureMemoryFilePath(); - - expect(result).toBe(defaultMemoryPath); - - // Verify migration happened - const newFileExists = await fs.access(newMemoryPath).then(() => true).catch(() => false); - const oldFileExists = await fs.access(oldMemoryPath).then(() => true).catch(() => false); - - expect(newFileExists).toBe(true); - expect(oldFileExists).toBe(false); - - // Verify console messages - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('DETECTED: Found legacy memory.json file') - ); - expect(consoleErrorSpy).toHaveBeenCalledWith( - expect.stringContaining('COMPLETED: Successfully migrated') - ); - - consoleErrorSpy.mockRestore(); - }); - - it('should use new file when both old and new files exist', async () => { - // Create both files - await fs.writeFile(oldMemoryPath, '{"old":"data"}'); - await fs.writeFile(newMemoryPath, '{"new":"data"}'); - - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - - const result = await ensureMemoryFilePath(); - - expect(result).toBe(defaultMemoryPath); - - // Verify no migration happened (both files should still exist) - const newFileExists = await fs.access(newMemoryPath).then(() => true).catch(() => false); - const oldFileExists = await fs.access(oldMemoryPath).then(() => true).catch(() => false); - - expect(newFileExists).toBe(true); - expect(oldFileExists).toBe(true); - - // Verify no console messages about migration - expect(consoleErrorSpy).not.toHaveBeenCalled(); - - consoleErrorSpy.mockRestore(); - }); - - it('should preserve file content during migration', async () => { - const testContent = '{"entities": [{"name": "test", "type": "person"}]}'; - await fs.writeFile(oldMemoryPath, testContent); - - await ensureMemoryFilePath(); - - const migratedContent = await fs.readFile(newMemoryPath, 'utf-8'); - expect(migratedContent).toBe(testContent); - }); - }); - - describe('defaultMemoryPath', () => { - it('should end with memory.jsonl', () => { - expect(defaultMemoryPath).toMatch(/memory\.jsonl$/); - }); - - it('should be an absolute path', () => { - expect(path.isAbsolute(defaultMemoryPath)).toBe(true); - }); - }); -}); - -describe('expandHome', () => { - it('expands a bare "~" to the home directory', () => { - expect(expandHome('~')).toBe(os.homedir()); - }); - - it('expands a leading "~/" to the home directory', () => { - expect(expandHome('~/notes/memory.jsonl')).toBe( - path.join(os.homedir(), 'notes/memory.jsonl') - ); - }); - - it('leaves a "~" not followed by a separator unchanged', () => { - expect(expandHome('~backup.jsonl')).toBe('~backup.jsonl'); - }); - - it('leaves absolute paths unchanged', () => { - expect(expandHome('/var/data/memory.jsonl')).toBe('/var/data/memory.jsonl'); - }); - - it('leaves relative paths unchanged', () => { - expect(expandHome(path.join('data', 'memory.jsonl'))).toBe( - path.join('data', 'memory.jsonl') - ); - }); -}); diff --git a/src/memory/__tests__/knowledge-graph.test.ts b/src/memory/__tests__/knowledge-graph.test.ts deleted file mode 100644 index 7d05a0c..0000000 --- a/src/memory/__tests__/knowledge-graph.test.ts +++ /dev/null @@ -1,774 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { promises as fs } from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { KnowledgeGraphManager, Entity, Relation, KnowledgeGraph } from '../index.js'; - -describe('KnowledgeGraphManager', () => { - let manager: KnowledgeGraphManager; - let testFilePath: string; - - beforeEach(async () => { - // Create a temporary test file path - testFilePath = path.join( - path.dirname(fileURLToPath(import.meta.url)), - `test-memory-${Date.now()}.jsonl` - ); - manager = new KnowledgeGraphManager(testFilePath); - }); - - afterEach(async () => { - // Clean up test file - try { - await fs.unlink(testFilePath); - } catch (error) { - // Ignore errors if file doesn't exist - } - }); - - describe('createEntities', () => { - it('should create new entities', async () => { - const entities: Entity[] = [ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - { name: 'Bob', entityType: 'person', observations: ['likes programming'] }, - ]; - - const newEntities = await manager.createEntities(entities); - expect(newEntities).toHaveLength(2); - expect(newEntities).toEqual(entities); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(2); - }); - - it('should not create duplicate entities', async () => { - const entities: Entity[] = [ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - ]; - - await manager.createEntities(entities); - const newEntities = await manager.createEntities(entities); - - expect(newEntities).toHaveLength(0); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(1); - }); - - it('should handle empty entity arrays', async () => { - const newEntities = await manager.createEntities([]); - expect(newEntities).toHaveLength(0); - }); - - it('should ignore duplicate entity names within a single batch', async () => { - const entities: Entity[] = [ - { name: 'Alice', entityType: 'person', observations: ['first'] }, - { name: 'Alice', entityType: 'person', observations: ['second'] }, - ]; - - const newEntities = await manager.createEntities(entities); - expect(newEntities).toHaveLength(1); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(1); - expect(graph.entities[0].name).toBe('Alice'); - }); - }); - - describe('createRelations', () => { - it('should create new relations', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - ]); - - const relations: Relation[] = [ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - ]; - - const newRelations = await manager.createRelations(relations); - expect(newRelations).toHaveLength(1); - expect(newRelations).toEqual(relations); - - const graph = await manager.readGraph(); - expect(graph.relations).toHaveLength(1); - }); - - it('should not create duplicate relations', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - ]); - - const relations: Relation[] = [ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - ]; - - await manager.createRelations(relations); - const newRelations = await manager.createRelations(relations); - - expect(newRelations).toHaveLength(0); - - const graph = await manager.readGraph(); - expect(graph.relations).toHaveLength(1); - }); - - it('should reject relations from non-existent entities', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - ]); - - await expect( - manager.createRelations([ - { from: 'Ghost', to: 'Alice', relationType: 'knows' }, - ]) - ).rejects.toThrow('Entity with name Ghost not found'); - - const graph = await manager.readGraph(); - expect(graph.relations).toHaveLength(0); - }); - - it('should reject relation batches that reference non-existent target entities', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - ]); - - await expect( - manager.createRelations([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - { from: 'Alice', to: 'Ghost', relationType: 'knows' }, - ]) - ).rejects.toThrow('Entity with name Ghost not found'); - - const graph = await manager.readGraph(); - expect(graph.relations).toHaveLength(0); - }); - - it('should handle empty relation arrays', async () => { - const newRelations = await manager.createRelations([]); - expect(newRelations).toHaveLength(0); - }); - - it('should skip duplicate relations within a single batch', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - ]); - - const relations: Relation[] = [ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - ]; - - const newRelations = await manager.createRelations(relations); - expect(newRelations).toHaveLength(1); - - const graph = await manager.readGraph(); - expect(graph.relations).toHaveLength(1); - }); - }); - - describe('addObservations', () => { - it('should add observations to existing entities', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - ]); - - const results = await manager.addObservations([ - { entityName: 'Alice', contents: ['likes coffee', 'has a dog'] }, - ]); - - expect(results).toHaveLength(1); - expect(results[0].entityName).toBe('Alice'); - expect(results[0].addedObservations).toHaveLength(2); - - const graph = await manager.readGraph(); - const alice = graph.entities.find(e => e.name === 'Alice'); - expect(alice?.observations).toHaveLength(3); - }); - - it('should not add duplicate observations', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - ]); - - await manager.addObservations([ - { entityName: 'Alice', contents: ['likes coffee'] }, - ]); - - const results = await manager.addObservations([ - { entityName: 'Alice', contents: ['likes coffee', 'has a dog'] }, - ]); - - expect(results[0].addedObservations).toHaveLength(1); - expect(results[0].addedObservations).toContain('has a dog'); - - const graph = await manager.readGraph(); - const alice = graph.entities.find(e => e.name === 'Alice'); - expect(alice?.observations).toHaveLength(3); - }); - - it('should throw error for non-existent entity', async () => { - await expect(async () => { - await manager.addObservations([ - { entityName: 'NonExistent', contents: ['some observation'] }, - ]); - }).rejects.toThrow('Entity with name NonExistent not found'); - }); - }); - - describe('deleteEntities', () => { - it('should delete entities', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - ]); - - await manager.deleteEntities(['Alice']); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(1); - expect(graph.entities[0].name).toBe('Bob'); - }); - - it('should cascade delete relations when deleting entities', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - { name: 'Charlie', entityType: 'person', observations: [] }, - ]); - - await manager.createRelations([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - { from: 'Bob', to: 'Charlie', relationType: 'knows' }, - ]); - - await manager.deleteEntities(['Bob']); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(2); - expect(graph.relations).toHaveLength(0); - }); - - it('should handle deleting non-existent entities', async () => { - await manager.deleteEntities(['NonExistent']); - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(0); - }); - }); - - describe('deleteObservations', () => { - it('should delete observations from entities', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp', 'likes coffee'] }, - ]); - - await manager.deleteObservations([ - { entityName: 'Alice', observations: ['likes coffee'] }, - ]); - - const graph = await manager.readGraph(); - const alice = graph.entities.find(e => e.name === 'Alice'); - expect(alice?.observations).toHaveLength(1); - expect(alice?.observations).toContain('works at Acme Corp'); - }); - - it('should handle deleting from non-existent entities', async () => { - await manager.deleteObservations([ - { entityName: 'NonExistent', observations: ['some observation'] }, - ]); - // Should not throw error - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(0); - }); - }); - - describe('deleteRelations', () => { - it('should delete specific relations', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - ]); - - await manager.createRelations([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - { from: 'Alice', to: 'Bob', relationType: 'works_with' }, - ]); - - await manager.deleteRelations([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - ]); - - const graph = await manager.readGraph(); - expect(graph.relations).toHaveLength(1); - expect(graph.relations[0].relationType).toBe('works_with'); - }); - }); - - describe('readGraph', () => { - it('should return empty graph when file does not exist', async () => { - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(0); - expect(graph.relations).toHaveLength(0); - }); - - it('should return complete graph with entities and relations', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }, - ]); - - await manager.createRelations([ - { from: 'Alice', to: 'Alice', relationType: 'self' }, - ]); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(1); - expect(graph.relations).toHaveLength(1); - }); - }); - - describe('searchNodes', () => { - beforeEach(async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme Corp', 'likes programming'] }, - { name: 'Bob', entityType: 'person', observations: ['works at TechCo'] }, - { name: 'Acme Corp', entityType: 'company', observations: ['tech company'] }, - ]); - - await manager.createRelations([ - { from: 'Alice', to: 'Acme Corp', relationType: 'works_at' }, - { from: 'Bob', to: 'Acme Corp', relationType: 'competitor' }, - ]); - }); - - it('should search by entity name', async () => { - const result = await manager.searchNodes('Alice'); - expect(result.entities).toHaveLength(1); - expect(result.entities[0].name).toBe('Alice'); - }); - - it('should search by entity type', async () => { - const result = await manager.searchNodes('company'); - expect(result.entities).toHaveLength(1); - expect(result.entities[0].name).toBe('Acme Corp'); - }); - - it('should search by observation content', async () => { - const result = await manager.searchNodes('programming'); - expect(result.entities).toHaveLength(1); - expect(result.entities[0].name).toBe('Alice'); - }); - - it('should be case insensitive', async () => { - const result = await manager.searchNodes('ALICE'); - expect(result.entities).toHaveLength(1); - expect(result.entities[0].name).toBe('Alice'); - }); - - it('should include relations where at least one endpoint matches', async () => { - const result = await manager.searchNodes('Acme'); - expect(result.entities).toHaveLength(2); // Alice and Acme Corp - // Both relations included: Alice โ†’ Acme Corp (Alice matched) and Bob โ†’ Acme Corp (Acme Corp matched) - expect(result.relations).toHaveLength(2); - }); - - it('should include outgoing relations to unmatched entities', async () => { - const result = await manager.searchNodes('Alice'); - expect(result.entities).toHaveLength(1); - // Alice โ†’ Acme Corp relation included because Alice is the source - expect(result.relations).toHaveLength(1); - expect(result.relations[0].from).toBe('Alice'); - expect(result.relations[0].to).toBe('Acme Corp'); - }); - - it('should return empty graph for no matches', async () => { - const result = await manager.searchNodes('NonExistent'); - expect(result.entities).toHaveLength(0); - expect(result.relations).toHaveLength(0); - }); - }); - - describe('openNodes', () => { - beforeEach(async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - { name: 'Charlie', entityType: 'person', observations: [] }, - ]); - - await manager.createRelations([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - { from: 'Bob', to: 'Charlie', relationType: 'knows' }, - ]); - }); - - it('should open specific nodes by name', async () => { - const result = await manager.openNodes(['Alice', 'Bob']); - expect(result.entities).toHaveLength(2); - expect(result.entities.map(e => e.name)).toContain('Alice'); - expect(result.entities.map(e => e.name)).toContain('Bob'); - }); - - it('should include all relations connected to opened nodes', async () => { - const result = await manager.openNodes(['Alice', 'Bob']); - // Alice โ†’ Bob (both endpoints opened) and Bob โ†’ Charlie (Bob is opened) - expect(result.relations).toHaveLength(2); - expect(result.relations.some(r => r.from === 'Alice' && r.to === 'Bob')).toBe(true); - expect(result.relations.some(r => r.from === 'Bob' && r.to === 'Charlie')).toBe(true); - }); - - it('should include relations connected to opened nodes', async () => { - const result = await manager.openNodes(['Bob']); - // Bob has two relations: Alice โ†’ Bob and Bob โ†’ Charlie - expect(result.relations).toHaveLength(2); - expect(result.relations.some(r => r.from === 'Alice' && r.to === 'Bob')).toBe(true); - expect(result.relations.some(r => r.from === 'Bob' && r.to === 'Charlie')).toBe(true); - }); - - it('should include outgoing relations to nodes not in the open set', async () => { - // This is the core bug fix for #3137: open_nodes should return - // relations FROM the opened node, even if the target is not opened - const result = await manager.openNodes(['Alice']); - expect(result.entities).toHaveLength(1); - expect(result.entities[0].name).toBe('Alice'); - // Alice โ†’ Bob relation is included because Alice is opened - expect(result.relations).toHaveLength(1); - expect(result.relations[0].from).toBe('Alice'); - expect(result.relations[0].to).toBe('Bob'); - }); - - it('should include incoming relations from nodes not in the open set', async () => { - const result = await manager.openNodes(['Charlie']); - expect(result.entities).toHaveLength(1); - // Bob โ†’ Charlie relation is included because Charlie is opened - expect(result.relations).toHaveLength(1); - expect(result.relations[0].from).toBe('Bob'); - expect(result.relations[0].to).toBe('Charlie'); - }); - - it('should handle opening non-existent nodes', async () => { - const result = await manager.openNodes(['NonExistent']); - expect(result.entities).toHaveLength(0); - }); - - it('should handle empty node list', async () => { - const result = await manager.openNodes([]); - expect(result.entities).toHaveLength(0); - expect(result.relations).toHaveLength(0); - }); - }); - - describe('file persistence', () => { - it('should persist data across manager instances', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['persistent data'] }, - ]); - - // Create new manager instance with same file path - const manager2 = new KnowledgeGraphManager(testFilePath); - const graph = await manager2.readGraph(); - - expect(graph.entities).toHaveLength(1); - expect(graph.entities[0].name).toBe('Alice'); - }); - - it('should handle JSONL format correctly', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - ]); - await manager.createRelations([ - { from: 'Alice', to: 'Alice', relationType: 'self' }, - ]); - - // Read file directly - const fileContent = await fs.readFile(testFilePath, 'utf-8'); - const lines = fileContent.split('\n').filter(line => line.trim()); - - expect(lines).toHaveLength(2); - expect(JSON.parse(lines[0])).toHaveProperty('type', 'entity'); - expect(JSON.parse(lines[1])).toHaveProperty('type', 'relation'); - }); - - it('should write a trailing newline to produce valid JSONL', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['test'] }, - ]); - - const fileContent = await fs.readFile(testFilePath, 'utf-8'); - expect(fileContent.endsWith('\n')).toBe(true); - }); - - it('should produce a file where every line is individually valid JSON', async () => { - // This test catches the bug where saveGraph wrote lines.join("\n") - // without a trailing newline. When the file was later appended to - // (e.g. by a concurrent process or external tool), the last JSON - // object and the new first JSON object ended up on the same line, - // producing invalid JSONL like: - // {"type":"entity","name":"Alice"}{"type":"relation","from":"Alice",...} - // which fails with: "Unexpected non-whitespace character after JSON - // at position N" - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['test'] }, - { name: 'Bob', entityType: 'person', observations: [] }, - ]); - await manager.createRelations([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - ]); - - const fileContent = await fs.readFile(testFilePath, 'utf-8'); - const allLines = fileContent.split('\n'); - - // Every non-empty line must be valid JSON on its own - for (const line of allLines) { - if (line.trim() === '') continue; - expect(() => JSON.parse(line)).not.toThrow(); - } - }); - - it('should not corrupt JSONL when content is appended to the file externally', async () => { - // Simulate the real-world corruption scenario: - // 1. saveGraph writes entities to the file - // 2. An external process appends a new JSON line to the file - // 3. loadGraph must still parse the file without errors - // - // Without a trailing newline on step 1, the appended content in - // step 2 lands on the same line as the last entity, producing - // invalid JSONL that breaks loadGraph. - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['original'] }, - ]); - - // Simulate an external append (e.g. another process, a script, or - // a crash-recovery replay). This is what triggers the bug: without - // a trailing newline, this JSON object concatenates onto line 1. - const externalLine = JSON.stringify({ - type: 'entity', - name: 'External', - entityType: 'person', - observations: ['appended externally'], - }); - await fs.appendFile(testFilePath, externalLine + '\n'); - - // A new manager instance forces a fresh loadGraph from disk - const manager2 = new KnowledgeGraphManager(testFilePath); - const graph = await manager2.readGraph(); - - // Both entities must load without a JSON parse error - expect(graph.entities).toHaveLength(2); - expect(graph.entities.map(e => e.name)).toContain('Alice'); - expect(graph.entities.map(e => e.name)).toContain('External'); - }); - - it('should strip type field from entities when loading from file', async () => { - // Create entities and relations (these get saved with type field) - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['test observation'] }, - { name: 'Bob', entityType: 'person', observations: [] }, - ]); - await manager.createRelations([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - ]); - - // Verify file contains type field (order may vary) - const fileContent = await fs.readFile(testFilePath, 'utf-8'); - const fileLines = fileContent.split('\n').filter(line => line.trim()); - const fileItems = fileLines.map(line => JSON.parse(line)); - const fileEntity = fileItems.find(item => item.type === 'entity'); - const fileRelation = fileItems.find(item => item.type === 'relation'); - expect(fileEntity).toBeDefined(); - expect(fileEntity).toHaveProperty('type', 'entity'); - expect(fileRelation).toBeDefined(); - expect(fileRelation).toHaveProperty('type', 'relation'); - - // Create new manager instance to force reload from file - const manager2 = new KnowledgeGraphManager(testFilePath); - const graph = await manager2.readGraph(); - - // Verify loaded entities don't have type field - expect(graph.entities).toHaveLength(2); - graph.entities.forEach(entity => { - expect(entity).not.toHaveProperty('type'); - expect(entity).toHaveProperty('name'); - expect(entity).toHaveProperty('entityType'); - expect(entity).toHaveProperty('observations'); - }); - - // Verify loaded relations don't have type field - expect(graph.relations).toHaveLength(1); - graph.relations.forEach(relation => { - expect(relation).not.toHaveProperty('type'); - expect(relation).toHaveProperty('from'); - expect(relation).toHaveProperty('to'); - expect(relation).toHaveProperty('relationType'); - }); - }); - - it('should strip type field from searchNodes results', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: ['works at Acme'] }, - ]); - await manager.createRelations([ - { from: 'Alice', to: 'Alice', relationType: 'self' }, - ]); - - // Create new manager instance to force reload from file - const manager2 = new KnowledgeGraphManager(testFilePath); - const result = await manager2.searchNodes('Alice'); - - // Verify search results don't have type field - expect(result.entities).toHaveLength(1); - expect(result.entities[0]).not.toHaveProperty('type'); - expect(result.entities[0].name).toBe('Alice'); - - expect(result.relations).toHaveLength(1); - expect(result.relations[0]).not.toHaveProperty('type'); - expect(result.relations[0].from).toBe('Alice'); - }); - - it('should strip type field from openNodes results', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - ]); - await manager.createRelations([ - { from: 'Alice', to: 'Bob', relationType: 'knows' }, - ]); - - // Create new manager instance to force reload from file - const manager2 = new KnowledgeGraphManager(testFilePath); - const result = await manager2.openNodes(['Alice', 'Bob']); - - // Verify open results don't have type field - expect(result.entities).toHaveLength(2); - result.entities.forEach(entity => { - expect(entity).not.toHaveProperty('type'); - }); - - expect(result.relations).toHaveLength(1); - expect(result.relations[0]).not.toHaveProperty('type'); - }); - }); - - describe('loadGraph validation', () => { - it('skips corrupt entities instead of crashing search', async () => { - const lines = [ - JSON.stringify({ type: 'entity', name: 'Alice', entityType: 'person', observations: ['works at Acme Corp'] }), - JSON.stringify({ type: 'entity', name: 'Broken', observations: ['missing entityType'] }), - JSON.stringify({ type: 'entity', name: 'BadObs', entityType: 'person', observations: ['ok', null] }), - ]; - await fs.writeFile(testFilePath, lines.join('\n') + '\n'); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(1); - expect(graph.entities[0].name).toBe('Alice'); - - // searchNodes must not throw even though the file contains corrupt entries - const result = await manager.searchNodes('Acme'); - expect(result.entities).toHaveLength(1); - expect(result.entities[0].name).toBe('Alice'); - }); - - it('skips corrupt relations', async () => { - const lines = [ - JSON.stringify({ type: 'entity', name: 'Alice', entityType: 'person', observations: [] }), - JSON.stringify({ type: 'relation', from: 'Alice', to: 'Bob' }), // missing relationType - JSON.stringify({ type: 'relation', from: 'Alice', to: 'Bob', relationType: 'knows' }), - ]; - await fs.writeFile(testFilePath, lines.join('\n') + '\n'); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(1); - expect(graph.relations).toHaveLength(1); - expect(graph.relations[0].relationType).toBe('knows'); - }); - - it('skips malformed JSON lines', async () => { - const lines = [ - JSON.stringify({ type: 'entity', name: 'Alice', entityType: 'person', observations: [] }), - '{this is not valid json', - JSON.stringify({ type: 'entity', name: 'Bob', entityType: 'person', observations: [] }), - ]; - await fs.writeFile(testFilePath, lines.join('\n') + '\n'); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(2); - expect(graph.entities.map(e => e.name)).toEqual(['Alice', 'Bob']); - }); - }); - - describe('concurrent mutations', () => { - // Regression test for #1819: concurrent tool calls each independently - // load the graph, mutate their own copy, and write it back. Without - // serialization, whichever write lands last silently discards the - // other's changes. All mutations below are fired without awaiting each - // other first, simulating multiple tool calls landing close together. - - it('should not lose entities created concurrently', async () => { - const batch1: Entity[] = Array.from({ length: 10 }, (_, i) => ({ - name: `batch1-entity-${i}`, - entityType: 'test', - observations: [], - })); - const batch2: Entity[] = Array.from({ length: 10 }, (_, i) => ({ - name: `batch2-entity-${i}`, - entityType: 'test', - observations: [], - })); - - // Fire both concurrently instead of awaiting sequentially. - await Promise.all([ - manager.createEntities(batch1), - manager.createEntities(batch2), - ]); - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(20); - expect(graph.entities.map(e => e.name).sort()).toEqual( - [...batch1, ...batch2].map(e => e.name).sort() - ); - }); - - it('should not lose relations created concurrently with entity creation', async () => { - await manager.createEntities([ - { name: 'Alice', entityType: 'person', observations: [] }, - { name: 'Bob', entityType: 'person', observations: [] }, - { name: 'Carol', entityType: 'person', observations: [] }, - ]); - - await Promise.all([ - manager.createRelations([{ from: 'Alice', to: 'Bob', relationType: 'knows' }]), - manager.createRelations([{ from: 'Bob', to: 'Carol', relationType: 'knows' }]), - manager.addObservations([ - { entityName: 'Alice', contents: ['likes coffee'] }, - ]), - ]); - - const graph = await manager.readGraph(); - expect(graph.relations).toHaveLength(2); - expect(graph.entities.find(e => e.name === 'Alice')?.observations).toContain('likes coffee'); - }); - - it('should keep the file valid JSONL after many concurrent mutations', async () => { - const operations = Array.from({ length: 25 }, (_, i) => - manager.createEntities([ - { name: `stress-entity-${i}`, entityType: 'test', observations: [] }, - ]) - ); - - await Promise.all(operations); - - const raw = await fs.readFile(testFilePath, 'utf-8'); - const lines = raw.split('\n').filter(line => line.trim() !== ''); - - // Every line must parse as valid JSON; a corrupted interleaved write - // would produce a truncated or malformed line here. - for (const line of lines) { - expect(() => JSON.parse(line)).not.toThrow(); - } - - const graph = await manager.readGraph(); - expect(graph.entities).toHaveLength(25); - }); - }); -}); diff --git a/src/memory/__tests__/resource.test.ts b/src/memory/__tests__/resource.test.ts deleted file mode 100644 index 4326330..0000000 --- a/src/memory/__tests__/resource.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { SubscribeRequestSchema, UnsubscribeRequestSchema } from '@modelcontextprotocol/sdk/types.js'; -import { - KnowledgeGraphManager, - registerKnowledgeGraphResource, - registerKnowledgeGraphSubscriptions, -} from '../index.js'; - -describe('knowledge-graph resource', () => { - it('registers with kebab-case name, correct URI, and JSON mime type', () => { - const mockServer = { registerResource: vi.fn() } as unknown as McpServer; - const manager = {} as KnowledgeGraphManager; - - registerKnowledgeGraphResource(mockServer, manager); - - expect(mockServer.registerResource).toHaveBeenCalledWith( - 'knowledge-graph', - 'memory://knowledge-graph', - expect.objectContaining({ - title: 'Knowledge Graph', - mimeType: 'application/json', - }), - expect.any(Function), - ); - }); - - it('handler returns the graph as JSON in the contents array', async () => { - const mockServer = { registerResource: vi.fn() } as unknown as McpServer; - const fakeGraph = { - entities: [{ name: 'Alice', entityType: 'person', observations: ['engineer'] }], - relations: [{ from: 'Alice', to: 'Acme', relationType: 'works_at' }], - }; - const manager = { - readGraph: vi.fn().mockResolvedValue(fakeGraph), - } as unknown as KnowledgeGraphManager; - - registerKnowledgeGraphResource(mockServer, manager); - - const handler = (mockServer.registerResource as ReturnType).mock.calls[0][3]; - const result = await handler(new URL('memory://knowledge-graph')); - - expect(result.contents).toHaveLength(1); - expect(result.contents[0].uri).toBe('memory://knowledge-graph'); - expect(result.contents[0].mimeType).toBe('application/json'); - expect(JSON.parse(result.contents[0].text)).toEqual(fakeGraph); - expect(manager.readGraph).toHaveBeenCalledOnce(); - }); -}); - -describe('knowledge-graph resource subscriptions', () => { - function makeMockServer() { - const inner = { - registerCapabilities: vi.fn(), - setRequestHandler: vi.fn(), - sendResourceUpdated: vi.fn(), - }; - const mockServer = { server: inner } as unknown as McpServer; - return { mockServer, inner }; - } - - function handlerFor(inner: ReturnType['inner'], schema: unknown) { - const call = inner.setRequestHandler.mock.calls.find((c) => c[0] === schema); - if (!call) throw new Error('handler not registered'); - return call[1] as (request: { params: { uri: string } }) => Promise; - } - - it('declares the resources.subscribe capability', () => { - const { mockServer, inner } = makeMockServer(); - - registerKnowledgeGraphSubscriptions(mockServer); - - expect(inner.registerCapabilities).toHaveBeenCalledWith({ - resources: { subscribe: true }, - }); - }); - - it('registers subscribe and unsubscribe request handlers', () => { - const { mockServer, inner } = makeMockServer(); - - registerKnowledgeGraphSubscriptions(mockServer); - - const schemas = inner.setRequestHandler.mock.calls.map((c) => c[0]); - expect(schemas).toContain(SubscribeRequestSchema); - expect(schemas).toContain(UnsubscribeRequestSchema); - }); - - it('subscribe and unsubscribe handlers acknowledge with an empty result', async () => { - const { mockServer, inner } = makeMockServer(); - - registerKnowledgeGraphSubscriptions(mockServer); - - const req = { params: { uri: 'memory://knowledge-graph' } }; - await expect(handlerFor(inner, SubscribeRequestSchema)(req)).resolves.toEqual({}); - await expect(handlerFor(inner, UnsubscribeRequestSchema)(req)).resolves.toEqual({}); - }); -}); diff --git a/src/memory/__tests__/search-nodes-schema.test.ts b/src/memory/__tests__/search-nodes-schema.test.ts deleted file mode 100644 index c03f384..0000000 --- a/src/memory/__tests__/search-nodes-schema.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { SearchNodesQuerySchema, SEARCH_QUERY_MAX_LENGTH } from '../index.js'; - -describe('search_nodes input schema', () => { - it('should accept a normal query', () => { - expect(SearchNodesQuerySchema.safeParse('Alice').success).toBe(true); - expect(SearchNodesQuerySchema.safeParse('works at Acme Corp').success).toBe(true); - }); - - it('should accept a query at exactly the max length', () => { - const atLimit = 'a'.repeat(SEARCH_QUERY_MAX_LENGTH); - expect(SearchNodesQuerySchema.safeParse(atLimit).success).toBe(true); - }); - - it('should reject a query longer than the max length', () => { - const oversized = 'a'.repeat(SEARCH_QUERY_MAX_LENGTH + 1); - const result = SearchNodesQuerySchema.safeParse(oversized); - expect(result.success).toBe(false); - }); - - it('should still reject non-string input', () => { - expect(SearchNodesQuerySchema.safeParse(42).success).toBe(false); - expect(SearchNodesQuerySchema.safeParse(null).success).toBe(false); - }); -}); diff --git a/src/memory/__tests__/server-version.test.ts b/src/memory/__tests__/server-version.test.ts deleted file mode 100644 index f9cf437..0000000 --- a/src/memory/__tests__/server-version.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { createRequire } from 'node:module'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { resolvePackageVersion, SERVER_VERSION } from '../version.js'; - -const packageJson = createRequire(import.meta.url)('../package.json') as { version: string }; - -describe('server version', () => { - it('uses package.json version for serverInfo', () => { - expect(SERVER_VERSION).toBe(packageJson.version); - expect(resolvePackageVersion()).toBe(packageJson.version); - }); - - it('resolves package.json from the dist layout', () => { - const distDir = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'dist'); - const distVersionPath = path.join(distDir, 'version.js'); - - expect(() => createRequire(distVersionPath)('./version.js')).not.toThrow(); - const distModule = createRequire(distVersionPath)('./version.js') as { - SERVER_VERSION: string; - }; - expect(distModule.SERVER_VERSION).toBe(packageJson.version); - }); -}); diff --git a/src/memory/index.ts b/src/memory/index.ts index d9f8148..ad3937c 100644 --- a/src/memory/index.ts +++ b/src/memory/index.ts @@ -1,156 +1,50 @@ #!/usr/bin/env node -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { SubscribeRequestSchema, UnsubscribeRequestSchema } from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; import { promises as fs } from 'fs'; import path from 'path'; -import os from 'os'; -import { randomBytes } from 'crypto'; import { fileURLToPath } from 'url'; -import { SERVER_VERSION } from './version.js'; -// Define memory file path using environment variable with fallback -export const defaultMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'memory.jsonl'); -// Expand a leading "~" to the user's home directory. MCP clients pass -// MEMORY_FILE_PATH from JSON config, where no shell performs tilde expansion, -// so an unexpanded "~" would otherwise be treated as a relative path and -// joined onto the package directory. Mirrors the helper of the same name in -// the filesystem server (src/filesystem/path-utils.ts). -export function expandHome(filepath: string): string { - if (filepath.startsWith('~/') || filepath === '~') { - return path.join(os.homedir(), filepath.slice(1)); - } - return filepath; -} - -// Handle backward compatibility: migrate memory.json to memory.jsonl if needed -export async function ensureMemoryFilePath(): Promise { - if (process.env.MEMORY_FILE_PATH) { - // Custom path provided. Expand a leading "~" first, then resolve relative - // paths against the package directory (absolute paths are used as-is). - const customPath = expandHome(process.env.MEMORY_FILE_PATH); - return path.isAbsolute(customPath) - ? customPath - : path.join(path.dirname(fileURLToPath(import.meta.url)), customPath); - } - - // No custom path set, check for backward compatibility migration - const oldMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'memory.json'); - const newMemoryPath = defaultMemoryPath; - - try { - // Check if old file exists and new file doesn't - await fs.access(oldMemoryPath); - try { - await fs.access(newMemoryPath); - // Both files exist, use new one (no migration needed) - return newMemoryPath; - } catch { - // Old file exists, new file doesn't - migrate - console.error('DETECTED: Found legacy memory.json file, migrating to memory.jsonl for JSONL format compatibility'); - await fs.rename(oldMemoryPath, newMemoryPath); - console.error('COMPLETED: Successfully migrated memory.json to memory.jsonl'); - return newMemoryPath; - } - } catch { - // Old file doesn't exist, use new path - return newMemoryPath; - } -} - -// Initialize memory file path (will be set during startup) -let MEMORY_FILE_PATH: string; +// Define the path to the JSONL file, you can change this to your desired local path +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const MEMORY_FILE_PATH = path.join(__dirname, 'memory.json'); // We are storing our memory using entities, relations, and observations in a graph structure -export interface Entity { +interface Entity { name: string; entityType: string; observations: string[]; } -export interface Relation { +interface Relation { from: string; to: string; relationType: string; } -export interface KnowledgeGraph { +interface KnowledgeGraph { entities: Entity[]; relations: Relation[]; } // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph -export class KnowledgeGraphManager { - constructor(private memoryFilePath: string) {} - - // Serializes all read-modify-write graph mutations behind a single queue. - // Without this, concurrent tool calls (e.g. multiple mutations dispatched - // from one LLM turn) each independently load the graph, mutate their own - // copy, and write it back โ€” so whichever write lands last silently - // overwrites the other's changes, and interleaved writes to the same file - // can corrupt it outright. See #1819. - private mutationQueue: Promise = Promise.resolve(); - - private async withLock(operation: () => Promise): Promise { - const result = this.mutationQueue.then(operation, operation); - // Always resolve the queue itself, even if this operation failed, so a - // single failed mutation doesn't permanently wedge every call after it. - // The failure still propagates normally to whoever awaited `result`. - this.mutationQueue = result.then( - () => undefined, - () => undefined, - ); - return result; - } - +class KnowledgeGraphManager { private async loadGraph(): Promise { try { - const data = await fs.readFile(this.memoryFilePath, "utf-8"); + const data = await fs.readFile(MEMORY_FILE_PATH, "utf-8"); const lines = data.split("\n").filter(line => line.trim() !== ""); - const graph: KnowledgeGraph = { entities: [], relations: [] }; - - for (const line of lines) { - let item: unknown; - try { - item = JSON.parse(line); - } catch { - console.error("Skipping malformed line in memory file"); - continue; - } - - if (typeof item !== "object" || item === null) { - console.error("Skipping non-object line in memory file"); - continue; - } - - const record = item as Record; - if (record.type === "entity") { - const parsed = EntitySchema.safeParse(item); - if (parsed.success) { - graph.entities.push(parsed.data); - } else { - console.error( - "Skipping invalid entity in memory file:", - parsed.error.issues.map(issue => `${issue.path.join(".")}: ${issue.message}`).join(", ") - ); - } - } else if (record.type === "relation") { - const parsed = RelationSchema.safeParse(item); - if (parsed.success) { - graph.relations.push(parsed.data); - } else { - console.error( - "Skipping invalid relation in memory file:", - parsed.error.issues.map(issue => `${issue.path.join(".")}: ${issue.message}`).join(", ") - ); - } - } - } - - return graph; + return lines.reduce((graph: KnowledgeGraph, line) => { + const item = JSON.parse(line); + if (item.type === "entity") graph.entities.push(item as Entity); + if (item.type === "relation") graph.relations.push(item as Relation); + return graph; + }, { entities: [], relations: [] }); } catch (error) { if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") { return { entities: [], relations: [] }; @@ -161,149 +55,73 @@ export class KnowledgeGraphManager { private async saveGraph(graph: KnowledgeGraph): Promise { const lines = [ - ...graph.entities.map(e => JSON.stringify({ - type: "entity", - name: e.name, - entityType: e.entityType, - observations: e.observations - })), - ...graph.relations.map(r => JSON.stringify({ - type: "relation", - from: r.from, - to: r.to, - relationType: r.relationType - })), + ...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })), + ...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })), ]; - - // Write to a temporary file in the same directory, then rename it over - // the target. fs.writeFile would truncate the memory file before writing, - // so an interruption (SIGKILL, container stop, OOM, power loss) would - // leave the only copy of the graph truncated and unrecoverable. - // rename(2) is atomic on POSIX filesystems: readers see either the - // complete old file or the complete new one, never a partial state. - // The temp file is kept in the same directory so the rename stays on one - // filesystem โ€” renaming across mount points fails with EXDEV. - const directory = path.dirname(this.memoryFilePath); - const tempFilePath = path.join( - directory, - `${path.basename(this.memoryFilePath)}.${randomBytes(16).toString('hex')}.tmp` - ); - - try { - await fs.writeFile(tempFilePath, lines.join("\n") + "\n"); - await fs.rename(tempFilePath, this.memoryFilePath); - } catch (error) { - // Never leave a stray temp file behind on failure. - await fs.unlink(tempFilePath).catch(() => {}); - throw error; - } + await fs.writeFile(MEMORY_FILE_PATH, lines.join("\n")); } async createEntities(entities: Entity[]): Promise { - return this.withLock(async () => { - const graph = await this.loadGraph(); - const newEntities = entities.filter((e, index) => - !graph.entities.some(existingEntity => existingEntity.name === e.name) && - // Also skip duplicates appearing earlier in this same batch - !entities.slice(0, index).some(earlier => earlier.name === e.name) - ); - graph.entities.push(...newEntities); - await this.saveGraph(graph); - return newEntities; - }); + const graph = await this.loadGraph(); + const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name)); + graph.entities.push(...newEntities); + await this.saveGraph(graph); + return newEntities; } async createRelations(relations: Relation[]): Promise { - return this.withLock(async () => { - const graph = await this.loadGraph(); - const entityNames = new Set(graph.entities.map(e => e.name)); - - relations.forEach(r => { - if (!entityNames.has(r.from)) { - throw new Error(`Entity with name ${r.from} not found`); - } - if (!entityNames.has(r.to)) { - throw new Error(`Entity with name ${r.to} not found`); - } - }); - - const isSameRelation = (a: Relation, b: Relation) => - a.from === b.from && - a.to === b.to && - a.relationType === b.relationType; - const newRelations = relations.filter((r, index) => - !graph.relations.some(existingRelation => isSameRelation(existingRelation, r)) && - // Also skip duplicates appearing earlier in this same batch - !relations.slice(0, index).some(earlier => isSameRelation(earlier, r)) - ); - graph.relations.push(...newRelations); - await this.saveGraph(graph); - return newRelations; - }); + const graph = await this.loadGraph(); + const newRelations = relations.filter(r => !graph.relations.some(existingRelation => + existingRelation.from === r.from && + existingRelation.to === r.to && + existingRelation.relationType === r.relationType + )); + graph.relations.push(...newRelations); + await this.saveGraph(graph); + return newRelations; } async addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: string[] }[]> { - return this.withLock(async () => { - const graph = await this.loadGraph(); - const results = observations.map(o => { - const entity = graph.entities.find(e => e.name === o.entityName); - if (!entity) { - throw new Error(`Entity with name ${o.entityName} not found`); - } - const newObservations = o.contents.filter(content => !entity.observations.includes(content)); - entity.observations.push(...newObservations); - return { entityName: o.entityName, addedObservations: newObservations }; - }); - await this.saveGraph(graph); - return results; + const graph = await this.loadGraph(); + const results = observations.map(o => { + const entity = graph.entities.find(e => e.name === o.entityName); + if (!entity) { + throw new Error(`Entity with name ${o.entityName} not found`); + } + const newObservations = o.contents.filter(content => !entity.observations.includes(content)); + entity.observations.push(...newObservations); + return { entityName: o.entityName, addedObservations: newObservations }; }); + await this.saveGraph(graph); + return results; } - async deleteEntities(entityNames: string[]): Promise<{ deleted: string[]; notFound: string[] }> { - return this.withLock(async () => { - const graph = await this.loadGraph(); - const present = new Set(graph.entities.map(e => e.name)); - const deleted = entityNames.filter(name => present.has(name)); - const notFound = entityNames.filter(name => !present.has(name)); - graph.entities = graph.entities.filter(e => !entityNames.includes(e.name)); - graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to)); - await this.saveGraph(graph); - return { deleted, notFound }; - }); + async deleteEntities(entityNames: string[]): Promise { + const graph = await this.loadGraph(); + graph.entities = graph.entities.filter(e => !entityNames.includes(e.name)); + graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to)); + await this.saveGraph(graph); } - async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<{ deletedCount: number; missingEntities: string[] }> { - return this.withLock(async () => { - const graph = await this.loadGraph(); - let deletedCount = 0; - const missingEntities: string[] = []; - deletions.forEach(d => { - const entity = graph.entities.find(e => e.name === d.entityName); - if (entity) { - const before = entity.observations.length; - entity.observations = entity.observations.filter(o => !d.observations.includes(o)); - deletedCount += before - entity.observations.length; - } else { - missingEntities.push(d.entityName); - } - }); - await this.saveGraph(graph); - return { deletedCount, missingEntities }; + async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise { + const graph = await this.loadGraph(); + deletions.forEach(d => { + const entity = graph.entities.find(e => e.name === d.entityName); + if (entity) { + entity.observations = entity.observations.filter(o => !d.observations.includes(o)); + } }); + await this.saveGraph(graph); } - async deleteRelations(relations: Relation[]): Promise<{ deletedCount: number }> { - return this.withLock(async () => { - const graph = await this.loadGraph(); - const before = graph.relations.length; - graph.relations = graph.relations.filter(r => !relations.some(delRelation => - r.from === delRelation.from && - r.to === delRelation.to && - r.relationType === delRelation.relationType - )); - await this.saveGraph(graph); - return { deletedCount: before - graph.relations.length }; - }); + async deleteRelations(relations: Relation[]): Promise { + const graph = await this.loadGraph(); + graph.relations = graph.relations.filter(r => !relations.some(delRelation => + r.from === delRelation.from && + r.to === delRelation.to && + r.relationType === delRelation.relationType + )); + await this.saveGraph(graph); } async readGraph(): Promise { @@ -324,10 +142,9 @@ export class KnowledgeGraphManager { // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); - // Include relations where at least one endpoint matches the search results. - // This lets callers discover connections to nodes outside the result set. + // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => - filteredEntityNames.has(r.from) || filteredEntityNames.has(r.to) + filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to) ); const filteredGraph: KnowledgeGraph = { @@ -347,12 +164,9 @@ export class KnowledgeGraphManager { // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); - // Include relations where at least one endpoint is in the requested set. - // Previously this required BOTH endpoints, which meant relations from a - // requested node to an unrequested node were silently dropped โ€” making it - // impossible to discover a node's connections without reading the full graph. + // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => - filteredEntityNames.has(r.from) || filteredEntityNames.has(r.to) + filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to) ); const filteredGraph: KnowledgeGraph = { @@ -364,376 +178,231 @@ export class KnowledgeGraphManager { } } -let knowledgeGraphManager: KnowledgeGraphManager; +const knowledgeGraphManager = new KnowledgeGraphManager(); -// Zod schemas for entities and relations -const EntitySchema = z.object({ - name: z.string().describe("The name of the entity"), - entityType: z.string().describe("The type of the entity"), - observations: z.array(z.string()).describe("An array of observation contents associated with the entity") -}); -const RelationSchema = z.object({ - from: z.string().describe("The name of the entity where the relation starts"), - to: z.string().describe("The name of the entity where the relation ends"), - relationType: z.string().describe("The type of the relation") -}); - -const server = new McpServer({ +// The server instance and tools exposed to Claude +const server = new Server({ name: "memory-server", - version: SERVER_VERSION, + version: "1.0.0", +}, { + capabilities: { + tools: {}, + }, + },); + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "create_entities", + description: "Create multiple new entities in the knowledge graph", + inputSchema: { + type: "object", + properties: { + entities: { + type: "array", + items: { + type: "object", + properties: { + name: { type: "string", description: "The name of the entity" }, + entityType: { type: "string", description: "The type of the entity" }, + observations: { + type: "array", + items: { type: "string" }, + description: "An array of observation contents associated with the entity" + }, + }, + required: ["name", "entityType", "observations"], + }, + }, + }, + required: ["entities"], + }, + }, + { + name: "create_relations", + description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice", + inputSchema: { + type: "object", + properties: { + relations: { + type: "array", + items: { + type: "object", + properties: { + from: { type: "string", description: "The name of the entity where the relation starts" }, + to: { type: "string", description: "The name of the entity where the relation ends" }, + relationType: { type: "string", description: "The type of the relation" }, + }, + required: ["from", "to", "relationType"], + }, + }, + }, + required: ["relations"], + }, + }, + { + name: "add_observations", + description: "Add new observations to existing entities in the knowledge graph", + inputSchema: { + type: "object", + properties: { + observations: { + type: "array", + items: { + type: "object", + properties: { + entityName: { type: "string", description: "The name of the entity to add the observations to" }, + contents: { + type: "array", + items: { type: "string" }, + description: "An array of observation contents to add" + }, + }, + required: ["entityName", "contents"], + }, + }, + }, + required: ["observations"], + }, + }, + { + name: "delete_entities", + description: "Delete multiple entities and their associated relations from the knowledge graph", + inputSchema: { + type: "object", + properties: { + entityNames: { + type: "array", + items: { type: "string" }, + description: "An array of entity names to delete" + }, + }, + required: ["entityNames"], + }, + }, + { + name: "delete_observations", + description: "Delete specific observations from entities in the knowledge graph", + inputSchema: { + type: "object", + properties: { + deletions: { + type: "array", + items: { + type: "object", + properties: { + entityName: { type: "string", description: "The name of the entity containing the observations" }, + observations: { + type: "array", + items: { type: "string" }, + description: "An array of observations to delete" + }, + }, + required: ["entityName", "observations"], + }, + }, + }, + required: ["deletions"], + }, + }, + { + name: "delete_relations", + description: "Delete multiple relations from the knowledge graph", + inputSchema: { + type: "object", + properties: { + relations: { + type: "array", + items: { + type: "object", + properties: { + from: { type: "string", description: "The name of the entity where the relation starts" }, + to: { type: "string", description: "The name of the entity where the relation ends" }, + relationType: { type: "string", description: "The type of the relation" }, + }, + required: ["from", "to", "relationType"], + }, + description: "An array of relations to delete" + }, + }, + required: ["relations"], + }, + }, + { + name: "read_graph", + description: "Read the entire knowledge graph", + inputSchema: { + type: "object", + properties: {}, + }, + }, + { + name: "search_nodes", + description: "Search for nodes in the knowledge graph based on a query", + inputSchema: { + type: "object", + properties: { + query: { type: "string", description: "The search query to match against entity names, types, and observation content" }, + }, + required: ["query"], + }, + }, + { + name: "open_nodes", + description: "Open specific nodes in the knowledge graph by their names", + inputSchema: { + type: "object", + properties: { + names: { + type: "array", + items: { type: "string" }, + description: "An array of entity names to retrieve", + }, + }, + required: ["names"], + }, + }, + ], + }; }); -const RESOURCE_URI = "memory://knowledge-graph"; +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; -// Track which resource URIs the connected client has subscribed to, so we only -// emit notifications/resources/updated to a client that asked for them. -const resourceSubscribers = new Set(); - -// Notify subscribers that the knowledge graph resource changed. No-op when the -// client has not subscribed. -function notifyGraphUpdated() { - if (resourceSubscribers.has(RESOURCE_URI)) { - server.server.sendResourceUpdated({ uri: RESOURCE_URI }); + if (!args) { + throw new Error(`No arguments provided for tool: ${name}`); } -} -// Register create_entities tool -server.registerTool( - "create_entities", - { - title: "Create Entities", - description: "Create multiple new entities in the knowledge graph", - inputSchema: { - entities: z.array(EntitySchema) - }, - outputSchema: { - entities: z.array(EntitySchema) - }, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - } - }, - async ({ entities }) => { - const result = await knowledgeGraphManager.createEntities(entities); - notifyGraphUpdated(); - return { - content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }], - structuredContent: { entities: result } - }; + switch (name) { + case "create_entities": + return { toolResult: await knowledgeGraphManager.createEntities(args.entities as Entity[]) }; + case "create_relations": + return { toolResult: await knowledgeGraphManager.createRelations(args.relations as Relation[]) }; + case "add_observations": + return { toolResult: await knowledgeGraphManager.addObservations(args.observations as { entityName: string; contents: string[] }[]) }; + case "delete_entities": + await knowledgeGraphManager.deleteEntities(args.entityNames as string[]); + return { toolResult: "Entities deleted successfully" }; + case "delete_observations": + await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]); + return { toolResult: "Observations deleted successfully" }; + case "delete_relations": + await knowledgeGraphManager.deleteRelations(args.relations as Relation[]); + return { toolResult: "Relations deleted successfully" }; + case "read_graph": + return { toolResult: await knowledgeGraphManager.readGraph() }; + case "search_nodes": + return { toolResult: await knowledgeGraphManager.searchNodes(args.query as string) }; + case "open_nodes": + return { toolResult: await knowledgeGraphManager.openNodes(args.names as string[]) }; + default: + throw new Error(`Unknown tool: ${name}`); } -); - -// Register create_relations tool -server.registerTool( - "create_relations", - { - title: "Create Relations", - description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice", - inputSchema: { - relations: z.array(RelationSchema) - }, - outputSchema: { - relations: z.array(RelationSchema) - }, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - } - }, - async ({ relations }) => { - const result = await knowledgeGraphManager.createRelations(relations); - notifyGraphUpdated(); - return { - content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }], - structuredContent: { relations: result } - }; - } -); - -// Register add_observations tool -server.registerTool( - "add_observations", - { - title: "Add Observations", - description: "Add new observations to existing entities in the knowledge graph", - inputSchema: { - observations: z.array(z.object({ - entityName: z.string().describe("The name of the entity to add the observations to"), - contents: z.array(z.string()).describe("An array of observation contents to add") - })) - }, - outputSchema: { - results: z.array(z.object({ - entityName: z.string(), - addedObservations: z.array(z.string()) - })) - }, - annotations: { - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - } - }, - async ({ observations }) => { - const result = await knowledgeGraphManager.addObservations(observations); - notifyGraphUpdated(); - return { - content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }], - structuredContent: { results: result } - }; - } -); - -// Register delete_entities tool -server.registerTool( - "delete_entities", - { - title: "Delete Entities", - description: "Delete multiple entities and their associated relations from the knowledge graph", - inputSchema: { - entityNames: z.array(z.string()).describe("An array of entity names to delete") - }, - outputSchema: { - success: z.boolean(), - message: z.string() - }, - annotations: { - readOnlyHint: false, - destructiveHint: true, - idempotentHint: true, - openWorldHint: false, - } - }, - async ({ entityNames }) => { - const { deleted, notFound } = await knowledgeGraphManager.deleteEntities(entityNames); - notifyGraphUpdated(); - const message = notFound.length === 0 - ? "Entities deleted successfully" - : `Deleted ${deleted.length} of ${entityNames.length} entities. Not found: ${notFound.join(", ")}`; - return { - content: [{ type: "text" as const, text: message }], - structuredContent: { success: true, message } - }; - } -); - -// Register delete_observations tool -server.registerTool( - "delete_observations", - { - title: "Delete Observations", - description: "Delete specific observations from entities in the knowledge graph", - inputSchema: { - deletions: z.array(z.object({ - entityName: z.string().describe("The name of the entity containing the observations"), - observations: z.array(z.string()).describe("An array of observations to delete") - })) - }, - outputSchema: { - success: z.boolean(), - message: z.string() - }, - annotations: { - readOnlyHint: false, - destructiveHint: true, - idempotentHint: true, - openWorldHint: false, - } - }, - async ({ deletions }) => { - const { deletedCount, missingEntities } = await knowledgeGraphManager.deleteObservations(deletions); - notifyGraphUpdated(); - const requested = deletions.reduce((total, d) => total + d.observations.length, 0); - const message = deletedCount === requested - ? "Observations deleted successfully" - : `Deleted ${deletedCount} of ${requested} observations.` + - (missingEntities.length ? ` Entities not found: ${missingEntities.join(", ")}` : ""); - return { - content: [{ type: "text" as const, text: message }], - structuredContent: { success: true, message } - }; - } -); - -// Register delete_relations tool -server.registerTool( - "delete_relations", - { - title: "Delete Relations", - description: "Delete multiple relations from the knowledge graph", - inputSchema: { - relations: z.array(RelationSchema).describe("An array of relations to delete") - }, - outputSchema: { - success: z.boolean(), - message: z.string() - }, - annotations: { - readOnlyHint: false, - destructiveHint: true, - idempotentHint: true, - openWorldHint: false, - } - }, - async ({ relations }) => { - const { deletedCount } = await knowledgeGraphManager.deleteRelations(relations); - notifyGraphUpdated(); - const message = deletedCount === relations.length - ? "Relations deleted successfully" - : `Deleted ${deletedCount} of ${relations.length} relations. The rest matched nothing.`; - return { - content: [{ type: "text" as const, text: message }], - structuredContent: { success: true, message } - }; - } -); - -// Register read_graph tool -server.registerTool( - "read_graph", - { - title: "Read Graph", - description: "Read the entire knowledge graph", - inputSchema: {}, - outputSchema: { - entities: z.array(EntitySchema), - relations: z.array(RelationSchema) - }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - } - }, - async () => { - const graph = await knowledgeGraphManager.readGraph(); - return { - content: [{ type: "text" as const, text: JSON.stringify(graph, null, 2) }], - structuredContent: { ...graph } - }; - } -); - -export const SEARCH_QUERY_MAX_LENGTH = 2048; - -export const SearchNodesQuerySchema = z - .string() - .max(SEARCH_QUERY_MAX_LENGTH) - .describe("The search query to match against entity names, types, and observation content"); - -// Register search_nodes tool -server.registerTool( - "search_nodes", - { - title: "Search Nodes", - description: "Search for nodes in the knowledge graph based on a query", - inputSchema: { - query: SearchNodesQuerySchema - }, - outputSchema: { - entities: z.array(EntitySchema), - relations: z.array(RelationSchema) - }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - } - }, - async ({ query }) => { - const graph = await knowledgeGraphManager.searchNodes(query); - return { - content: [{ type: "text" as const, text: JSON.stringify(graph, null, 2) }], - structuredContent: { ...graph } - }; - } -); - -// Register open_nodes tool -server.registerTool( - "open_nodes", - { - title: "Open Nodes", - description: "Open specific nodes in the knowledge graph by their names", - inputSchema: { - names: z.array(z.string()).describe("An array of entity names to retrieve") - }, - outputSchema: { - entities: z.array(EntitySchema), - relations: z.array(RelationSchema) - }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - } - }, - async ({ names }) => { - const graph = await knowledgeGraphManager.openNodes(names); - return { - content: [{ type: "text" as const, text: JSON.stringify(graph, null, 2) }], - structuredContent: { ...graph } - }; - } -); - -export function registerKnowledgeGraphResource( - server: McpServer, - manager: KnowledgeGraphManager, -) { - server.registerResource( - "knowledge-graph", - RESOURCE_URI, - { - title: "Knowledge Graph", - description: "The full knowledge graph with all entities and relations", - mimeType: "application/json", - }, - async (uri) => { - const graph = await manager.readGraph(); - return { - contents: [ - { - uri: uri.href, - mimeType: "application/json", - text: JSON.stringify(graph, null, 2), - }, - ], - }; - }, - ); -} - -// Enable clients to subscribe to the knowledge-graph resource and receive -// notifications/resources/updated when mutation tools change the graph. -export function registerKnowledgeGraphSubscriptions(server: McpServer) { - server.server.registerCapabilities({ resources: { subscribe: true } }); - server.server.setRequestHandler(SubscribeRequestSchema, async (request) => { - resourceSubscribers.add(request.params.uri); - return {}; - }); - server.server.setRequestHandler(UnsubscribeRequestSchema, async (request) => { - resourceSubscribers.delete(request.params.uri); - return {}; - }); -} +}); async function main() { - MEMORY_FILE_PATH = await ensureMemoryFilePath(); - knowledgeGraphManager = new KnowledgeGraphManager(MEMORY_FILE_PATH); - registerKnowledgeGraphResource(server, knowledgeGraphManager); - registerKnowledgeGraphSubscriptions(server); - const transport = new StdioServerTransport(); await server.connect(transport); console.error("Knowledge Graph MCP Server running on stdio"); @@ -742,4 +411,4 @@ async function main() { main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); -}); +}); \ No newline at end of file diff --git a/src/memory/package.json b/src/memory/package.json index ea10979..32bb098 100644 --- a/src/memory/package.json +++ b/src/memory/package.json @@ -1,16 +1,11 @@ { "name": "@modelcontextprotocol/server-memory", - "version": "0.6.3", + "version": "0.5.1", "description": "MCP server for enabling memory for Claude through a knowledge graph", - "license": "SEE LICENSE IN LICENSE", - "mcpName": "io.github.modelcontextprotocol/server-memory", - "author": "Model Context Protocol a Series of LF Projects, LLC.", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", "homepage": "https://modelcontextprotocol.io", "bugs": "https://github.com/modelcontextprotocol/servers/issues", - "repository": { - "type": "git", - "url": "https://github.com/modelcontextprotocol/servers.git" - }, "type": "module", "bin": { "mcp-server-memory": "dist/index.js" @@ -21,18 +16,14 @@ "scripts": { "build": "tsc && shx chmod +x dist/*.js", "prepare": "npm run build", - "watch": "tsc --watch", - "test": "vitest run --coverage" + "watch": "tsc --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "zod": "^4.0.0" + "@modelcontextprotocol/sdk": "0.5.0" }, "devDependencies": { - "@types/node": "^22", - "@vitest/coverage-v8": "^4.1.8", - "shx": "^0.4.0", - "typescript": "^5.6.2", - "vitest": "^4.1.8" + "@types/node": "^22.9.3", + "shx": "^0.3.4", + "typescript": "^5.6.2" } -} \ No newline at end of file +} diff --git a/src/memory/tsconfig.json b/src/memory/tsconfig.json index d2d8655..4d33cae 100644 --- a/src/memory/tsconfig.json +++ b/src/memory/tsconfig.json @@ -1,14 +1,11 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "." - }, - "include": [ - "./**/*.ts" - ], - "exclude": [ - "**/*.test.ts", - "vitest.config.ts" - ] -} + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] + } + \ No newline at end of file diff --git a/src/memory/version.ts b/src/memory/version.ts deleted file mode 100644 index f1df958..0000000 --- a/src/memory/version.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createRequire } from 'node:module'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -export function resolvePackageVersion(): string { - const require = createRequire(import.meta.url); - const moduleDir = path.dirname(fileURLToPath(import.meta.url)); - const candidates = [ - path.join(moduleDir, 'package.json'), - path.join(moduleDir, '..', 'package.json'), - ]; - - for (const candidate of candidates) { - try { - const pkg = require(candidate) as { version?: string }; - if (pkg.version) { - return pkg.version; - } - } catch { - // Try the next candidate when running from dist/ or source. - } - } - - throw new Error('Could not locate package.json for server version'); -} - -export const SERVER_VERSION = resolvePackageVersion(); diff --git a/src/memory/vitest.config.ts b/src/memory/vitest.config.ts deleted file mode 100644 index d414ec8..0000000 --- a/src/memory/vitest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - include: ['**/__tests__/**/*.test.ts'], - coverage: { - provider: 'v8', - include: ['**/*.ts'], - exclude: ['**/__tests__/**', '**/dist/**'], - }, - }, -}); diff --git a/src/postgres/README.md b/src/postgres/README.md new file mode 100644 index 0000000..9a16af7 --- /dev/null +++ b/src/postgres/README.md @@ -0,0 +1,46 @@ +# PostgreSQL + +A Model Context Protocol server that provides read-only access to PostgreSQL databases. This server enables LLMs to inspect database schemas and execute read-only queries. + +## Components + +### Tools + +- **query** + - Execute read-only SQL queries against the connected database + - Input: `sql` (string): The SQL query to execute + - All queries are executed within a READ ONLY transaction + +### Resources + +The server provides schema information for each table in the database: + +- **Table Schemas** (`postgres:////schema`) + - JSON schema information for each table + - Includes column names and data types + - Automatically discovered from database metadata + +## Usage with Claude Desktop + +To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "postgres": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-postgres", + "postgresql://localhost/mydb" + ] + } + } +} +``` + +Replace `/mydb` with your database name. + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/postgres/index.ts b/src/postgres/index.ts new file mode 100644 index 0000000..24fabf3 --- /dev/null +++ b/src/postgres/index.ts @@ -0,0 +1,143 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import pg from "pg"; + +const server = new Server( + { + name: "example-servers/postgres", + version: "0.1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + }, +); + +const args = process.argv.slice(2); +if (args.length === 0) { + console.error("Please provide a database URL as a command-line argument"); + process.exit(1); +} + +const databaseUrl = args[0]; + +const resourceBaseUrl = new URL(databaseUrl); +resourceBaseUrl.protocol = "postgres:"; +resourceBaseUrl.password = ""; + +const pool = new pg.Pool({ + connectionString: databaseUrl, +}); + +const SCHEMA_PATH = "schema"; + +server.setRequestHandler(ListResourcesRequestSchema, async () => { + const client = await pool.connect(); + try { + const result = await client.query( + "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'", + ); + return { + resources: result.rows.map((row) => ({ + uri: new URL(`${row.table_name}/${SCHEMA_PATH}`, resourceBaseUrl).href, + mimeType: "application/json", + name: `"${row.table_name}" database schema`, + })), + }; + } finally { + client.release(); + } +}); + +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const resourceUrl = new URL(request.params.uri); + + const pathComponents = resourceUrl.pathname.split("/"); + const schema = pathComponents.pop(); + const tableName = pathComponents.pop(); + + if (schema !== SCHEMA_PATH) { + throw new Error("Invalid resource URI"); + } + + const client = await pool.connect(); + try { + const result = await client.query( + "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1", + [tableName], + ); + + return { + contents: [ + { + uri: request.params.uri, + mimeType: "application/json", + text: JSON.stringify(result.rows, null, 2), + }, + ], + }; + } finally { + client.release(); + } +}); + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "query", + description: "Run a read-only SQL query", + inputSchema: { + type: "object", + properties: { + sql: { type: "string" }, + }, + }, + }, + ], + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name === "query") { + const sql = request.params.arguments?.sql as string; + + const client = await pool.connect(); + try { + await client.query("BEGIN TRANSACTION READ ONLY"); + const result = await client.query(sql); + return { + content: [{ type: "text", text: JSON.stringify(result.rows, null, 2) }], + isError: false, + }; + } catch (error) { + throw error; + } finally { + client + .query("ROLLBACK") + .catch((error) => + console.warn("Could not roll back transaction:", error), + ); + + client.release(); + } + } + throw new Error(`Unknown tool: ${request.params.name}`); +}); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +runServer().catch(console.error); diff --git a/src/postgres/package.json b/src/postgres/package.json new file mode 100644 index 0000000..243d2de --- /dev/null +++ b/src/postgres/package.json @@ -0,0 +1,30 @@ +{ + "name": "@modelcontextprotocol/server-postgres", + "version": "0.5.1", + "description": "MCP server for interacting with PostgreSQL databases", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-postgres": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "pg": "^8.13.0" + }, + "devDependencies": { + "@types/pg": "^8.11.10", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/postgres/tsconfig.json b/src/postgres/tsconfig.json new file mode 100644 index 0000000..ec5da15 --- /dev/null +++ b/src/postgres/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] +} diff --git a/src/puppeteer/README.md b/src/puppeteer/README.md new file mode 100644 index 0000000..a951a9a --- /dev/null +++ b/src/puppeteer/README.md @@ -0,0 +1,81 @@ +# Puppeteer + +A Model Context Protocol server that provides browser automation capabilities using Puppeteer. This server enables LLMs to interact with web pages, take screenshots, and execute JavaScript in a real browser environment. + +## Components + +### Tools + +- **puppeteer_navigate** + - Navigate to any URL in the browser + - Input: `url` (string) + +- **puppeteer_screenshot** + - Capture screenshots of the entire page or specific elements + - Inputs: + - `name` (string, required): Name for the screenshot + - `selector` (string, optional): CSS selector for element to screenshot + - `width` (number, optional, default: 800): Screenshot width + - `height` (number, optional, default: 600): Screenshot height + +- **puppeteer_click** + - Click elements on the page + - Input: `selector` (string): CSS selector for element to click + +- **puppeteer_hover** + - Hover elements on the page + - Input: `selector` (string): CSS selector for element to hover + +- **puppeteer_fill** + - Fill out input fields + - Inputs: + - `selector` (string): CSS selector for input field + - `value` (string): Value to fill + +- **puppeteer_select** + - Select an element with SELECT tag + - Inputs: + - `selector` (string): CSS selector for element to select + - `value` (string): Value to select + +- **puppeteer_evaluate** + - Execute JavaScript in the browser console + - Input: `script` (string): JavaScript code to execute + +### Resources + +The server provides access to two types of resources: + +1. **Console Logs** (`console://logs`) + - Browser console output in text format + - Includes all console messages from the browser + +2. **Screenshots** (`screenshot://`) + - PNG images of captured screenshots + - Accessible via the screenshot name specified during capture + +## Key Features + +- Browser automation +- Console log monitoring +- Screenshot capabilities +- JavaScript execution +- Basic web interaction (navigation, clicking, form filling) + +## Configuration to use Puppeteer Server +Here's the Claude Desktop configuration to use the Puppeter server: + +```json +{ + "mcpServers": { + "puppeteer": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-puppeteer"] + } + } +} +``` + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/puppeteer/index.ts b/src/puppeteer/index.ts new file mode 100644 index 0000000..d3aa2a3 --- /dev/null +++ b/src/puppeteer/index.ts @@ -0,0 +1,418 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListResourcesRequestSchema, + ListToolsRequestSchema, + ReadResourceRequestSchema, + CallToolResult, + TextContent, + ImageContent, + Tool, +} from "@modelcontextprotocol/sdk/types.js"; +import puppeteer, { Browser, Page } from "puppeteer"; + +// Define the tools once to avoid repetition +const TOOLS: Tool[] = [ + { + name: "puppeteer_navigate", + description: "Navigate to a URL", + inputSchema: { + type: "object", + properties: { + url: { type: "string" }, + }, + required: ["url"], + }, + }, + { + name: "puppeteer_screenshot", + description: "Take a screenshot of the current page or a specific element", + inputSchema: { + type: "object", + properties: { + name: { type: "string", description: "Name for the screenshot" }, + selector: { type: "string", description: "CSS selector for element to screenshot" }, + width: { type: "number", description: "Width in pixels (default: 800)" }, + height: { type: "number", description: "Height in pixels (default: 600)" }, + }, + required: ["name"], + }, + }, + { + name: "puppeteer_click", + description: "Click an element on the page", + inputSchema: { + type: "object", + properties: { + selector: { type: "string", description: "CSS selector for element to click" }, + }, + required: ["selector"], + }, + }, + { + name: "puppeteer_fill", + description: "Fill out an input field", + inputSchema: { + type: "object", + properties: { + selector: { type: "string", description: "CSS selector for input field" }, + value: { type: "string", description: "Value to fill" }, + }, + required: ["selector", "value"], + }, + }, + { + name: "puppeteer_select", + description: "Select an element on the page with Select tag", + inputSchema: { + type: "object", + properties: { + selector: { type: "string", description: "CSS selector for element to select" }, + value: { type: "string", description: "Value to select" }, + }, + required: ["selector", "value"], + }, + }, + { + name: "puppeteer_hover", + description: "Hover an element on the page", + inputSchema: { + type: "object", + properties: { + selector: { type: "string", description: "CSS selector for element to hover" }, + }, + required: ["selector"], + }, + }, + { + name: "puppeteer_evaluate", + description: "Execute JavaScript in the browser console", + inputSchema: { + type: "object", + properties: { + script: { type: "string", description: "JavaScript code to execute" }, + }, + required: ["script"], + }, + }, +]; + +// Global state +let browser: Browser | undefined; +let page: Page | undefined; +const consoleLogs: string[] = []; +const screenshots = new Map(); + +async function ensureBrowser() { + if (!browser) { + browser = await puppeteer.launch({ headless: false }); + const pages = await browser.pages(); + page = pages[0]; + + page.on("console", (msg) => { + const logEntry = `[${msg.type()}] ${msg.text()}`; + consoleLogs.push(logEntry); + server.notification({ + method: "notifications/resources/updated", + params: { uri: "console://logs" }, + }); + }); + } + return page!; +} + +async function handleToolCall(name: string, args: any): Promise<{ toolResult: CallToolResult }> { + const page = await ensureBrowser(); + + switch (name) { + case "puppeteer_navigate": + await page.goto(args.url); + return { + toolResult: { + content: [{ + type: "text", + text: `Navigated to ${args.url}`, + }], + isError: false, + }, + }; + + case "puppeteer_screenshot": { + const width = args.width ?? 800; + const height = args.height ?? 600; + await page.setViewport({ width, height }); + + const screenshot = await (args.selector ? + (await page.$(args.selector))?.screenshot({ encoding: "base64" }) : + page.screenshot({ encoding: "base64", fullPage: false })); + + if (!screenshot) { + return { + toolResult: { + content: [{ + type: "text", + text: args.selector ? `Element not found: ${args.selector}` : "Screenshot failed", + }], + isError: true, + }, + }; + } + + screenshots.set(args.name, screenshot as string); + server.notification({ + method: "notifications/resources/list_changed", + }); + + return { + toolResult: { + content: [ + { + type: "text", + text: `Screenshot '${args.name}' taken at ${width}x${height}`, + } as TextContent, + { + type: "image", + data: screenshot, + mimeType: "image/png", + } as ImageContent, + ], + isError: false, + }, + }; + } + + case "puppeteer_click": + try { + await page.click(args.selector); + return { + toolResult: { + content: [{ + type: "text", + text: `Clicked: ${args.selector}`, + }], + isError: false, + }, + }; + } catch (error) { + return { + toolResult: { + content: [{ + type: "text", + text: `Failed to click ${args.selector}: ${(error as Error).message}`, + }], + isError: true, + }, + }; + } + + case "puppeteer_fill": + try { + await page.waitForSelector(args.selector); + await page.type(args.selector, args.value); + return { + toolResult: { + content: [{ + type: "text", + text: `Filled ${args.selector} with: ${args.value}`, + }], + isError: false, + }, + }; + } catch (error) { + return { + toolResult: { + content: [{ + type: "text", + text: `Failed to fill ${args.selector}: ${(error as Error).message}`, + }], + isError: true, + }, + }; + } + + case "puppeteer_select": + try { + await page.waitForSelector(args.selector); + await page.select(args.selector, args.value); + return { + toolResult: { + content: [{ + type: "text", + text: `Selected ${args.selector} with: ${args.value}`, + }], + isError: false, + }, + }; + } catch (error) { + return { + toolResult: { + content: [{ + type: "text", + text: `Failed to select ${args.selector}: ${(error as Error).message}`, + }], + isError: true, + }, + }; + } + + case "puppeteer_hover": + try { + await page.waitForSelector(args.selector); + await page.hover(args.selector); + return { + toolResult: { + content: [{ + type: "text", + text: `Hovered ${args.selector}`, + }], + isError: false, + }, + }; + } catch (error) { + return { + toolResult: { + content: [{ + type: "text", + text: `Failed to hover ${args.selector}: ${(error as Error).message}`, + }], + isError: true, + }, + }; + } + + case "puppeteer_evaluate": + try { + const result = await page.evaluate((script) => { + const logs: string[] = []; + const originalConsole = { ...console }; + + ['log', 'info', 'warn', 'error'].forEach(method => { + (console as any)[method] = (...args: any[]) => { + logs.push(`[${method}] ${args.join(' ')}`); + (originalConsole as any)[method](...args); + }; + }); + + try { + const result = eval(script); + Object.assign(console, originalConsole); + return { result, logs }; + } catch (error) { + Object.assign(console, originalConsole); + throw error; + } + }, args.script); + + return { + toolResult: { + content: [ + { + type: "text", + text: `Execution result:\n${JSON.stringify(result.result, null, 2)}\n\nConsole output:\n${result.logs.join('\n')}`, + }, + ], + isError: false, + }, + }; + } catch (error) { + return { + toolResult: { + content: [{ + type: "text", + text: `Script execution failed: ${(error as Error).message}`, + }], + isError: true, + }, + }; + } + + default: + return { + toolResult: { + content: [{ + type: "text", + text: `Unknown tool: ${name}`, + }], + isError: true, + }, + }; + } +} + +const server = new Server( + { + name: "example-servers/puppeteer", + version: "0.1.0", + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + }, +); + + +// Setup request handlers +server.setRequestHandler(ListResourcesRequestSchema, async () => ({ + resources: [ + { + uri: "console://logs", + mimeType: "text/plain", + name: "Browser console logs", + }, + ...Array.from(screenshots.keys()).map(name => ({ + uri: `screenshot://${name}`, + mimeType: "image/png", + name: `Screenshot: ${name}`, + })), + ], +})); + +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const uri = request.params.uri.toString(); + + if (uri === "console://logs") { + return { + contents: [{ + uri, + mimeType: "text/plain", + text: consoleLogs.join("\n"), + }], + }; + } + + if (uri.startsWith("screenshot://")) { + const name = uri.split("://")[1]; + const screenshot = screenshots.get(name); + if (screenshot) { + return { + contents: [{ + uri, + mimeType: "image/png", + blob: screenshot, + }], + }; + } + } + + throw new Error(`Resource not found: ${uri}`); +}); + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: TOOLS, +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => + handleToolCall(request.params.name, request.params.arguments ?? {}) +); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +runServer().catch(console.error); \ No newline at end of file diff --git a/src/puppeteer/package.json b/src/puppeteer/package.json new file mode 100644 index 0000000..b6f9d8c --- /dev/null +++ b/src/puppeteer/package.json @@ -0,0 +1,29 @@ +{ + "name": "@modelcontextprotocol/server-puppeteer", + "version": "0.5.1", + "description": "MCP server for browser automation using Puppeteer", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-puppeteer": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "0.5.0", + "puppeteer": "^23.4.0" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/puppeteer/tsconfig.json b/src/puppeteer/tsconfig.json new file mode 100644 index 0000000..ec5da15 --- /dev/null +++ b/src/puppeteer/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] +} diff --git a/src/sentry/.python-version b/src/sentry/.python-version new file mode 100644 index 0000000..c8cfe39 --- /dev/null +++ b/src/sentry/.python-version @@ -0,0 +1 @@ +3.10 diff --git a/src/sentry/README.md b/src/sentry/README.md new file mode 100644 index 0000000..34a1feb --- /dev/null +++ b/src/sentry/README.md @@ -0,0 +1,133 @@ +# mcp-server-sentry: A Sentry MCP server + +## Overview + +A Model Context Protocol server for retrieving and analyzing issues from Sentry.io. This server provides tools to inspect error reports, stacktraces, and other debugging information from your Sentry account. + +### Tools + +1. `get-sentry-issue` + - Retrieve and analyze a Sentry issue by ID or URL + - Input: + - `issue_id_or_url` (string): Sentry issue ID or URL to analyze + - Returns: Issue details including: + - Title + - Issue ID + - Status + - Level + - First seen timestamp + - Last seen timestamp + - Event count + - Full stacktrace + +### Prompts + +1. `sentry-issue` + - Retrieve issue details from Sentry + - Input: + - `issue_id_or_url` (string): Sentry issue ID or URL + - Returns: Formatted issue details as conversation context + +## Installation + +### Using uv (recommended) + +When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed. We will +use [`uvx`](https://docs.astral.sh/uv/guides/tools/) to directly run *mcp-server-sentry*. + +### Using PIP + +Alternatively you can install `mcp-server-sentry` via pip: + +``` +pip install mcp-server-sentry +``` + +After installation, you can run it as a script using: + +``` +python -m mcp_server_sentry +``` + +## Configuration + +### Usage with Claude Desktop + +Add this to your `claude_desktop_config.json`: + +
+Using uvx + +```json +"mcpServers": { + "sentry": { + "command": "uvx", + "args": ["mcp-server-sentry", "--auth-token", "YOUR_SENTRY_TOKEN"] + } +} +``` +
+ +
+Using pip installation + +```json +"mcpServers": { + "sentry": { + "command": "python", + "args": ["-m", "mcp_server_sentry", "--auth-token", "YOUR_SENTRY_TOKEN"] + } +} +``` +
+ +### Usage with [Zed](https://github.com/zed-industries/zed) + +Add to your Zed settings.json: + +
+Using uvx + +```json +"context_servers": [ + "mcp-server-sentry": { + "command": { + "path": "uvx", + "args": ["mcp-server-sentry", "--auth-token", "YOUR_SENTRY_TOKEN"] + } + } +], +``` +
+ +
+Using pip installation + +```json +"context_servers": { + "mcp-server-sentry": { + "command": "python", + "args": ["-m", "mcp_server_sentry", "--auth-token", "YOUR_SENTRY_TOKEN"] + } +}, +``` +
+ +## Debugging + +You can use the MCP inspector to debug the server. For uvx installations: + +``` +npx @modelcontextprotocol/inspector uvx mcp-server-sentry --auth-token YOUR_SENTRY_TOKEN +``` + +Or if you've installed the package in a specific directory or are developing on it: + +``` +cd path/to/servers/src/sentry +npx @modelcontextprotocol/inspector uv run mcp-server-sentry --auth-token YOUR_SENTRY_TOKEN +``` + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/sentry/pyproject.toml b/src/sentry/pyproject.toml new file mode 100644 index 0000000..7fd2778 --- /dev/null +++ b/src/sentry/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "mcp-server-sentry" +version = "0.6.1" +description = "MCP server for retrieving issues from sentry.io" +readme = "README.md" +requires-python = ">=3.10" +dependencies = ["mcp>=1.0.0"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.uv] +dev-dependencies = ["pyright>=1.1.389", "pytest>=8.3.3", "ruff>=0.8.0"] + +[project.scripts] +mcp-server-sentry = "mcp_server_sentry:main" diff --git a/src/sentry/src/mcp_server_sentry/__init__.py b/src/sentry/src/mcp_server_sentry/__init__.py new file mode 100644 index 0000000..8fc0980 --- /dev/null +++ b/src/sentry/src/mcp_server_sentry/__init__.py @@ -0,0 +1,11 @@ +from . import server +import asyncio + + +def main(): + """Main entry point for the package.""" + asyncio.run(server.main()) + + +# Optionally expose other important items at package level +__all__ = ["main", "server"] diff --git a/src/sentry/src/mcp_server_sentry/server.py b/src/sentry/src/mcp_server_sentry/server.py new file mode 100644 index 0000000..1760311 --- /dev/null +++ b/src/sentry/src/mcp_server_sentry/server.py @@ -0,0 +1,285 @@ +import asyncio +from dataclasses import dataclass +from urllib.parse import urlparse + +import click +import httpx +import mcp.types as types +from mcp.server import NotificationOptions, Server +from mcp.server.models import InitializationOptions +from mcp.shared.exceptions import McpError +import mcp.server.stdio + +SENTRY_API_BASE = "https://sentry.io/api/0/" +MISSING_AUTH_TOKEN_MESSAGE = ( + """Sentry authentication token not found. Please specify your Sentry auth token.""" +) + + +@dataclass +class SentryIssueData: + title: str + issue_id: str + status: str + level: str + first_seen: str + last_seen: str + count: int + stacktrace: str + + def to_text(self) -> str: + return f""" +Sentry Issue: {self.title} +Issue ID: {self.issue_id} +Status: {self.status} +Level: {self.level} +First Seen: {self.first_seen} +Last Seen: {self.last_seen} +Event Count: {self.count} + +{self.stacktrace} + """ + + def to_prompt_result(self) -> types.GetPromptResult: + return types.GetPromptResult( + description=f"Sentry Issue: {self.title}", + messages=[ + types.PromptMessage( + role="user", content=types.TextContent(type="text", text=self.to_text()) + ) + ], + ) + + def to_tool_result(self) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: + return [types.TextContent(type="text", text=self.to_text())] + + +class SentryError(Exception): + pass + + +def extract_issue_id(issue_id_or_url: str) -> str: + """ + Extracts the Sentry issue ID from either a full URL or a standalone ID. + + This function validates the input and returns the numeric issue ID. + It raises SentryError for invalid inputs, including empty strings, + non-Sentry URLs, malformed paths, and non-numeric IDs. + """ + if not issue_id_or_url: + raise SentryError("Missing issue_id_or_url argument") + + if issue_id_or_url.startswith(("http://", "https://")): + parsed_url = urlparse(issue_id_or_url) + if not parsed_url.hostname or not parsed_url.hostname.endswith(".sentry.io"): + raise SentryError("Invalid Sentry URL. Must be a URL ending with .sentry.io") + + path_parts = parsed_url.path.strip("/").split("/") + if len(path_parts) < 2 or path_parts[0] != "issues": + raise SentryError( + "Invalid Sentry issue URL. Path must contain '/issues/{issue_id}'" + ) + + issue_id = path_parts[-1] + else: + issue_id = issue_id_or_url + + if not issue_id.isdigit(): + raise SentryError("Invalid Sentry issue ID. Must be a numeric value.") + + return issue_id + + +def create_stacktrace(latest_event: dict) -> str: + """ + Creates a formatted stacktrace string from the latest Sentry event. + + This function extracts exception information and stacktrace details from the + provided event dictionary, formatting them into a human-readable string. + It handles multiple exceptions and includes file, line number, and function + information for each frame in the stacktrace. + + Args: + latest_event (dict): A dictionary containing the latest Sentry event data. + + Returns: + str: A formatted string containing the stacktrace information, + or "No stacktrace found" if no relevant data is present. + """ + stacktraces = [] + for entry in latest_event.get("entries", []): + if entry["type"] != "exception": + continue + + exception_data = entry["data"]["values"] + for exception in exception_data: + exception_type = exception.get("type", "Unknown") + exception_value = exception.get("value", "") + stacktrace = exception.get("stacktrace") + + stacktrace_text = f"Exception: {exception_type}: {exception_value}\n\n" + if stacktrace: + stacktrace_text += "Stacktrace:\n" + for frame in stacktrace.get("frames", []): + filename = frame.get("filename", "Unknown") + lineno = frame.get("lineNo", "?") + function = frame.get("function", "Unknown") + + stacktrace_text += f"{filename}:{lineno} in {function}\n" + + if "context" in frame: + context = frame["context"] + for ctx_line in context: + stacktrace_text += f" {ctx_line[1]}\n" + + stacktrace_text += "\n" + + stacktraces.append(stacktrace_text) + + return "\n".join(stacktraces) if stacktraces else "No stacktrace found" + + +async def handle_sentry_issue( + http_client: httpx.AsyncClient, auth_token: str, issue_id_or_url: str +) -> SentryIssueData: + try: + issue_id = extract_issue_id(issue_id_or_url) + + response = await http_client.get( + f"issues/{issue_id}/", headers={"Authorization": f"Bearer {auth_token}"} + ) + if response.status_code == 401: + raise McpError( + "Error: Unauthorized. Please check your MCP_SENTRY_AUTH_TOKEN token." + ) + response.raise_for_status() + issue_data = response.json() + + # Get issue hashes + hashes_response = await http_client.get( + f"issues/{issue_id}/hashes/", + headers={"Authorization": f"Bearer {auth_token}"}, + ) + hashes_response.raise_for_status() + hashes = hashes_response.json() + + if not hashes: + raise McpError("No Sentry events found for this issue") + + latest_event = hashes[0]["latestEvent"] + stacktrace = create_stacktrace(latest_event) + + return SentryIssueData( + title=issue_data["title"], + issue_id=issue_id, + status=issue_data["status"], + level=issue_data["level"], + first_seen=issue_data["firstSeen"], + last_seen=issue_data["lastSeen"], + count=issue_data["count"], + stacktrace=stacktrace + ) + + except SentryError as e: + raise McpError(str(e)) + except httpx.HTTPStatusError as e: + raise McpError(f"Error fetching Sentry issue: {str(e)}") + except Exception as e: + raise McpError(f"An error occurred: {str(e)}") + + +async def serve(auth_token: str) -> Server: + server = Server("sentry") + http_client = httpx.AsyncClient(base_url=SENTRY_API_BASE) + + @server.list_prompts() + async def handle_list_prompts() -> list[types.Prompt]: + return [ + types.Prompt( + name="sentry-issue", + description="Retrieve a Sentry issue by ID or URL", + arguments=[ + types.PromptArgument( + name="issue_id_or_url", + description="Sentry issue ID or URL", + required=True, + ) + ], + ) + ] + + @server.get_prompt() + async def handle_get_prompt( + name: str, arguments: dict[str, str] | None + ) -> types.GetPromptResult: + if name != "sentry-issue": + raise ValueError(f"Unknown prompt: {name}") + + issue_id_or_url = (arguments or {}).get("issue_id_or_url", "") + issue_data = await handle_sentry_issue(http_client, auth_token, issue_id_or_url) + return issue_data.to_prompt_result() + + @server.list_tools() + async def handle_list_tools() -> list[types.Tool]: + return [ + types.Tool( + name="get-sentry-issue", + description="""Retrieve and analyze a Sentry issue by ID or URL. Use this tool when you need to: + - Investigate production errors and crashes + - Access detailed stacktraces from Sentry + - Analyze error patterns and frequencies + - Get information about when issues first/last occurred + - Review error counts and status""", + inputSchema={ + "type": "object", + "properties": { + "issue_id_or_url": { + "type": "string", + "description": "Sentry issue ID or URL to analyze" + } + }, + "required": ["issue_id_or_url"] + } + ) + ] + + @server.call_tool() + async def handle_call_tool( + name: str, arguments: dict | None + ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: + if name != "get-sentry-issue": + raise ValueError(f"Unknown tool: {name}") + + if not arguments or "issue_id_or_url" not in arguments: + raise ValueError("Missing issue_id_or_url argument") + + issue_data = await handle_sentry_issue(http_client, auth_token, arguments["issue_id_or_url"]) + return issue_data.to_tool_result() + + return server + +@click.command() +@click.option( + "--auth-token", + envvar="SENTRY_TOKEN", + required=True, + help="Sentry authentication token", +) +def main(auth_token: str): + async def _run(): + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): + server = await serve(auth_token) + await server.run( + read_stream, + write_stream, + InitializationOptions( + server_name="sentry", + server_version="0.4.1", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, + ), + ), + ) + + asyncio.run(_run()) diff --git a/src/sentry/uv.lock b/src/sentry/uv.lock new file mode 100644 index 0000000..02c4a90 --- /dev/null +++ b/src/sentry/uv.lock @@ -0,0 +1,439 @@ +version = 1 +requires-python = ">=3.10" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.6.2.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, +] + +[[package]] +name = "httpx" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/df/676b7cf674dd1bdc71a64ad393c89879f75e4a0ab8395165b498262ae106/httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0", size = 141307 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/fb/a19866137577ba60c6d8b69498dc36be479b13ba454f691348ddf428f185/httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc", size = 73551 }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "mcp" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "sse-starlette" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/de/a9ec0a1b6439f90ea59f89004bb2e7ec6890dfaeef809751d9e6577dca7e/mcp-1.0.0.tar.gz", hash = "sha256:dba51ce0b5c6a80e25576f606760c49a91ee90210fed805b530ca165d3bbc9b7", size = 82891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/89/900c0c8445ec001d3725e475fc553b0feb2e8a51be018f3bb7de51e683db/mcp-1.0.0-py3-none-any.whl", hash = "sha256:bbe70ffa3341cd4da78b5eb504958355c68381fb29971471cea1e642a2af5b8a", size = 36361 }, +] + +[[package]] +name = "mcp-server-sentry" +version = "0.6.0" +source = { editable = "." } +dependencies = [ + { name = "mcp" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pyright" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [{ name = "mcp", specifier = ">=1.0.0" }] + +[package.metadata.requires-dev] +dev = [ + { name = "pyright", specifier = ">=1.1.389" }, + { name = "pytest", specifier = ">=8.3.3" }, + { name = "ruff", specifier = ">=0.8.0" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pydantic" +version = "2.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/86/a03390cb12cf64e2a8df07c267f3eb8d5035e0f9a04bb20fb79403d2a00e/pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa", size = 785401 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/74/da832196702d0c56eb86b75bfa346db9238617e29b0b7ee3b8b4eccfe654/pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e", size = 456364 }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/ce/60fd96895c09738648c83f3f00f595c807cb6735c70d3306b548cc96dd49/pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a", size = 1897984 }, + { url = "https://files.pythonhosted.org/packages/fd/b9/84623d6b6be98cc209b06687d9bca5a7b966ffed008d15225dd0d20cce2e/pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b", size = 1807491 }, + { url = "https://files.pythonhosted.org/packages/01/72/59a70165eabbc93b1111d42df9ca016a4aa109409db04304829377947028/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278", size = 1831953 }, + { url = "https://files.pythonhosted.org/packages/7c/0c/24841136476adafd26f94b45bb718a78cb0500bd7b4f8d667b67c29d7b0d/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05", size = 1856071 }, + { url = "https://files.pythonhosted.org/packages/53/5e/c32957a09cceb2af10d7642df45d1e3dbd8596061f700eac93b801de53c0/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4", size = 2038439 }, + { url = "https://files.pythonhosted.org/packages/e4/8f/979ab3eccd118b638cd6d8f980fea8794f45018255a36044dea40fe579d4/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f", size = 2787416 }, + { url = "https://files.pythonhosted.org/packages/02/1d/00f2e4626565b3b6d3690dab4d4fe1a26edd6a20e53749eb21ca892ef2df/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08", size = 2134548 }, + { url = "https://files.pythonhosted.org/packages/9d/46/3112621204128b90898adc2e721a3cd6cf5626504178d6f32c33b5a43b79/pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6", size = 1989882 }, + { url = "https://files.pythonhosted.org/packages/49/ec/557dd4ff5287ffffdf16a31d08d723de6762bb1b691879dc4423392309bc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807", size = 1995829 }, + { url = "https://files.pythonhosted.org/packages/6e/b2/610dbeb74d8d43921a7234555e4c091cb050a2bdb8cfea86d07791ce01c5/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c", size = 2091257 }, + { url = "https://files.pythonhosted.org/packages/8c/7f/4bf8e9d26a9118521c80b229291fa9558a07cdd9a968ec2d5c1026f14fbc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206", size = 2143894 }, + { url = "https://files.pythonhosted.org/packages/1f/1c/875ac7139c958f4390f23656fe696d1acc8edf45fb81e4831960f12cd6e4/pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c", size = 1816081 }, + { url = "https://files.pythonhosted.org/packages/d7/41/55a117acaeda25ceae51030b518032934f251b1dac3704a53781383e3491/pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17", size = 1981109 }, + { url = "https://files.pythonhosted.org/packages/27/39/46fe47f2ad4746b478ba89c561cafe4428e02b3573df882334bd2964f9cb/pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8", size = 1895553 }, + { url = "https://files.pythonhosted.org/packages/1c/00/0804e84a78b7fdb394fff4c4f429815a10e5e0993e6ae0e0b27dd20379ee/pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330", size = 1807220 }, + { url = "https://files.pythonhosted.org/packages/01/de/df51b3bac9820d38371f5a261020f505025df732ce566c2a2e7970b84c8c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52", size = 1829727 }, + { url = "https://files.pythonhosted.org/packages/5f/d9/c01d19da8f9e9fbdb2bf99f8358d145a312590374d0dc9dd8dbe484a9cde/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4", size = 1854282 }, + { url = "https://files.pythonhosted.org/packages/5f/84/7db66eb12a0dc88c006abd6f3cbbf4232d26adfd827a28638c540d8f871d/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c", size = 2037437 }, + { url = "https://files.pythonhosted.org/packages/34/ac/a2537958db8299fbabed81167d58cc1506049dba4163433524e06a7d9f4c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de", size = 2780899 }, + { url = "https://files.pythonhosted.org/packages/4a/c1/3e38cd777ef832c4fdce11d204592e135ddeedb6c6f525478a53d1c7d3e5/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025", size = 2135022 }, + { url = "https://files.pythonhosted.org/packages/7a/69/b9952829f80fd555fe04340539d90e000a146f2a003d3fcd1e7077c06c71/pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e", size = 1987969 }, + { url = "https://files.pythonhosted.org/packages/05/72/257b5824d7988af43460c4e22b63932ed651fe98804cc2793068de7ec554/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919", size = 1994625 }, + { url = "https://files.pythonhosted.org/packages/73/c3/78ed6b7f3278a36589bcdd01243189ade7fc9b26852844938b4d7693895b/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c", size = 2090089 }, + { url = "https://files.pythonhosted.org/packages/8d/c8/b4139b2f78579960353c4cd987e035108c93a78371bb19ba0dc1ac3b3220/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc", size = 2142496 }, + { url = "https://files.pythonhosted.org/packages/3e/f8/171a03e97eb36c0b51981efe0f78460554a1d8311773d3d30e20c005164e/pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9", size = 1811758 }, + { url = "https://files.pythonhosted.org/packages/6a/fe/4e0e63c418c1c76e33974a05266e5633e879d4061f9533b1706a86f77d5b/pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5", size = 1980864 }, + { url = "https://files.pythonhosted.org/packages/50/fc/93f7238a514c155a8ec02fc7ac6376177d449848115e4519b853820436c5/pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89", size = 1864327 }, + { url = "https://files.pythonhosted.org/packages/be/51/2e9b3788feb2aebff2aa9dfbf060ec739b38c05c46847601134cc1fed2ea/pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", size = 1895239 }, + { url = "https://files.pythonhosted.org/packages/7b/9e/f8063952e4a7d0127f5d1181addef9377505dcce3be224263b25c4f0bfd9/pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", size = 1805070 }, + { url = "https://files.pythonhosted.org/packages/2c/9d/e1d6c4561d262b52e41b17a7ef8301e2ba80b61e32e94520271029feb5d8/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", size = 1828096 }, + { url = "https://files.pythonhosted.org/packages/be/65/80ff46de4266560baa4332ae3181fffc4488ea7d37282da1a62d10ab89a4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", size = 1857708 }, + { url = "https://files.pythonhosted.org/packages/d5/ca/3370074ad758b04d9562b12ecdb088597f4d9d13893a48a583fb47682cdf/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", size = 2037751 }, + { url = "https://files.pythonhosted.org/packages/b1/e2/4ab72d93367194317b99d051947c071aef6e3eb95f7553eaa4208ecf9ba4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", size = 2733863 }, + { url = "https://files.pythonhosted.org/packages/8a/c6/8ae0831bf77f356bb73127ce5a95fe115b10f820ea480abbd72d3cc7ccf3/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", size = 2161161 }, + { url = "https://files.pythonhosted.org/packages/f1/f4/b2fe73241da2429400fc27ddeaa43e35562f96cf5b67499b2de52b528cad/pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", size = 1993294 }, + { url = "https://files.pythonhosted.org/packages/77/29/4bb008823a7f4cc05828198153f9753b3bd4c104d93b8e0b1bfe4e187540/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", size = 2001468 }, + { url = "https://files.pythonhosted.org/packages/f2/a9/0eaceeba41b9fad851a4107e0cf999a34ae8f0d0d1f829e2574f3d8897b0/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", size = 2091413 }, + { url = "https://files.pythonhosted.org/packages/d8/36/eb8697729725bc610fd73940f0d860d791dc2ad557faaefcbb3edbd2b349/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", size = 2154735 }, + { url = "https://files.pythonhosted.org/packages/52/e5/4f0fbd5c5995cc70d3afed1b5c754055bb67908f55b5cb8000f7112749bf/pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", size = 1833633 }, + { url = "https://files.pythonhosted.org/packages/ee/f2/c61486eee27cae5ac781305658779b4a6b45f9cc9d02c90cb21b940e82cc/pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", size = 1986973 }, + { url = "https://files.pythonhosted.org/packages/df/a6/e3f12ff25f250b02f7c51be89a294689d175ac76e1096c32bf278f29ca1e/pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", size = 1883215 }, + { url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033 }, + { url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542 }, + { url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854 }, + { url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389 }, + { url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934 }, + { url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176 }, + { url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720 }, + { url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972 }, + { url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477 }, + { url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186 }, + { url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429 }, + { url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713 }, + { url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897 }, + { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 }, + { url = "https://files.pythonhosted.org/packages/7c/60/e5eb2d462595ba1f622edbe7b1d19531e510c05c405f0b87c80c1e89d5b1/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6", size = 1894016 }, + { url = "https://files.pythonhosted.org/packages/61/20/da7059855225038c1c4326a840908cc7ca72c7198cb6addb8b92ec81c1d6/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676", size = 1771648 }, + { url = "https://files.pythonhosted.org/packages/8f/fc/5485cf0b0bb38da31d1d292160a4d123b5977841ddc1122c671a30b76cfd/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d", size = 1826929 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/fb1284a210e13a5f34c639efc54d51da136074ffbe25ec0c279cf9fbb1c4/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c", size = 1980591 }, + { url = "https://files.pythonhosted.org/packages/f1/14/77c1887a182d05af74f6aeac7b740da3a74155d3093ccc7ee10b900cc6b5/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27", size = 1981326 }, + { url = "https://files.pythonhosted.org/packages/06/aa/6f1b2747f811a9c66b5ef39d7f02fbb200479784c75e98290d70004b1253/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f", size = 1989205 }, + { url = "https://files.pythonhosted.org/packages/7a/d2/8ce2b074d6835f3c88d85f6d8a399790043e9fdb3d0e43455e72d19df8cc/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed", size = 2079616 }, + { url = "https://files.pythonhosted.org/packages/65/71/af01033d4e58484c3db1e5d13e751ba5e3d6b87cc3368533df4c50932c8b/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f", size = 2133265 }, + { url = "https://files.pythonhosted.org/packages/33/72/f881b5e18fbb67cf2fb4ab253660de3c6899dbb2dba409d0b757e3559e3d/pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c", size = 2001864 }, +] + +[[package]] +name = "pyright" +version = "1.1.389" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/4e/9a5ab8745e7606b88c2c7ca223449ac9d82a71fd5e31df47b453f2cb39a1/pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220", size = 21940 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/26/c288cabf8cfc5a27e1aa9e5029b7682c0f920b8074f45d22bf844314d66a/pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60", size = 18581 }, +] + +[[package]] +name = "pytest" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, +] + +[[package]] +name = "ruff" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605 }, + { url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243 }, + { url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739 }, + { url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153 }, + { url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387 }, + { url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351 }, + { url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879 }, + { url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354 }, + { url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976 }, + { url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564 }, + { url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604 }, + { url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071 }, + { url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657 }, + { url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362 }, + { url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476 }, + { url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463 }, + { url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "sse-starlette" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383 }, +] + +[[package]] +name = "starlette" +version = "0.41.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "uvicorn" +version = "0.32.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 }, +] diff --git a/src/sequentialthinking/Dockerfile b/src/sequentialthinking/Dockerfile deleted file mode 100644 index f1a8819..0000000 --- a/src/sequentialthinking/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM node:22.12-alpine AS builder - -COPY src/sequentialthinking /app -COPY tsconfig.json /tsconfig.json - -WORKDIR /app - -RUN --mount=type=cache,target=/root/.npm npm install - -RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev - -FROM node:22-alpine AS release - -COPY --from=builder /app/dist /app/dist -COPY --from=builder /app/package.json /app/package.json -COPY --from=builder /app/package-lock.json /app/package-lock.json - -ENV NODE_ENV=production - -WORKDIR /app - -RUN npm ci --ignore-scripts --omit-dev - -ENTRYPOINT ["node", "dist/index.js"] diff --git a/src/sequentialthinking/README.md b/src/sequentialthinking/README.md deleted file mode 100644 index b6b853a..0000000 --- a/src/sequentialthinking/README.md +++ /dev/null @@ -1,222 +0,0 @@ -# Sequential Thinking MCP Server - -An MCP server implementation that provides a tool for dynamic and reflective problem-solving through a structured thinking process. - -Published on npm as [`@modelcontextprotocol/server-sequential-thinking`](https://www.npmjs.com/package/@modelcontextprotocol/server-sequential-thinking). - -## Features - -- Break down complex problems into manageable steps -- Revise and refine thoughts as understanding deepens -- Branch into alternative paths of reasoning -- Adjust the total number of thoughts dynamically -- Generate and verify solution hypotheses - -## Tool - -### sequential_thinking - -Facilitates a detailed, step-by-step thinking process for problem-solving and analysis. - -**Inputs:** -- `thought` (string): The current thinking step -- `nextThoughtNeeded` (boolean): Whether another thought step is needed -- `thoughtNumber` (integer): Current thought number -- `totalThoughts` (integer): Estimated total thoughts needed -- `isRevision` (boolean, optional): Whether this revises previous thinking -- `revisesThought` (integer, optional): Which thought is being reconsidered -- `branchFromThought` (integer, optional): Branching point thought number -- `branchId` (string, optional): Branch identifier -- `needsMoreThoughts` (boolean, optional): If more thoughts are needed - -## Usage - -The Sequential Thinking tool is designed for: -- Breaking down complex problems into steps -- Planning and design with room for revision -- Analysis that might need course correction -- Problems where the full scope might not be clear initially -- Tasks that need to maintain context over multiple steps -- Situations where irrelevant information needs to be filtered out - -In practice, you do not call `sequential_thinking` directly by hand unless your client exposes raw tool calls. Instead, connect the server to an MCP-aware host and ask the model to think through a problem step by step. The host can then decide to call the tool one or more times while it works. - -### What it looks like in use - -Example prompts that typically benefit from this tool: - -- `Plan a database migration from PostgreSQL 14 to 16, list risks, and revise the plan if downtime exceeds 5 minutes.` -- `Debug why this deployment only fails in production and show your reasoning step by step.` -- `Compare three architecture options for a file sync engine and branch if one assumption turns out to be wrong.` - -### How to tell it is working - -If your host or inspector shows tool activity, you should see repeated calls to `sequential_thinking` with fields such as: - -- `thought` -- `thoughtNumber` -- `totalThoughts` -- `nextThoughtNeeded` - -When the reasoning changes course, you may also see revision or branching fields like `isRevision`, `revisesThought`, `branchFromThought`, or `branchId`. - -### Quick manual verification - -After installing the server in your MCP host: - -1. Restart or reload the host so it reconnects to the server. -2. Confirm the `sequential_thinking` tool appears in the host's MCP tool list or inspector. -3. Ask the host to solve a non-trivial problem in a step-by-step way. -4. Verify that the host invokes the tool multiple times instead of returning a one-shot answer. - -## Configuration - -### Usage with Claude Desktop - -Add this to your `claude_desktop_config.json`: - -#### npx - -```json -{ - "mcpServers": { - "sequential-thinking": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-sequential-thinking" - ] - } - } -} -``` - -On Windows, use `cmd /c` to launch `npx`: - -```json -{ - "mcpServers": { - "sequential-thinking": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-sequential-thinking" - ] - } - } -} -``` - -#### docker - -```json -{ - "mcpServers": { - "sequentialthinking": { - "command": "docker", - "args": [ - "run", - "--rm", - "-i", - "mcp/sequentialthinking" - ] - } - } -} -``` - -To disable logging of thought information set env var: `DISABLE_THOUGHT_LOGGING` to `true`. - -### Usage with VS Code - -For quick installation, click one of the installation buttons below... - -[![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=sequentialthinking&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-sequential-thinking%22%5D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=sequentialthinking&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-sequential-thinking%22%5D%7D&quality=insiders) - -[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=sequentialthinking&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22mcp%2Fsequentialthinking%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=sequentialthinking&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22mcp%2Fsequentialthinking%22%5D%7D&quality=insiders) - -For manual installation, you can configure the MCP server using one of these methods: - -**Method 1: User Configuration (Recommended)** -Add the configuration to your user-level MCP configuration file. Open the Command Palette (`Ctrl + Shift + P`) and run `MCP: Open User Configuration`. This will open your user `mcp.json` file where you can add the server configuration. - -**Method 2: Workspace Configuration** -Alternatively, you can add the configuration to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. - -> For more details about MCP configuration in VS Code, see the [official VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers). - -For NPX installation: - -```json -{ - "servers": { - "sequential-thinking": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-sequential-thinking" - ] - } - } -} -``` - -On Windows, use: - -```json -{ - "servers": { - "sequential-thinking": { - "command": "cmd", - "args": [ - "/c", - "npx", - "-y", - "@modelcontextprotocol/server-sequential-thinking" - ] - } - } -} -``` - -For Docker installation: - -```json -{ - "servers": { - "sequential-thinking": { - "command": "docker", - "args": [ - "run", - "--rm", - "-i", - "mcp/sequentialthinking" - ] - } - } -} -``` - -### Usage with Codex CLI - -Run the following: - -#### npx - -```bash -codex mcp add sequential-thinking npx -y @modelcontextprotocol/server-sequential-thinking -``` - -## Building - -Docker: - -```bash -docker build -t mcp/sequentialthinking -f src/sequentialthinking/Dockerfile . -``` - -## License - -This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/sequentialthinking/__tests__/input-schema.test.ts b/src/sequentialthinking/__tests__/input-schema.test.ts deleted file mode 100644 index 4ff7be6..0000000 --- a/src/sequentialthinking/__tests__/input-schema.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, it, expect, beforeAll, afterAll } from 'vitest'; -import { existsSync } from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; - -const packageRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); -const distIndexPath = path.join(packageRoot, 'dist', 'index.js'); - -// Regression coverage for #4651: nextThoughtNeeded must stay in the advertised -// `required` array, and string coercion must keep accepting "True"/"FALSE" -// while rejecting anything else. Runs against the built server so it checks -// the schema the SDK actually emits, not the zod object. -describe.skipIf(!existsSync(distIndexPath))('sequentialthinking input schema', () => { - let client: Client; - - beforeAll(async () => { - const transport = new StdioClientTransport({ - command: process.execPath, - args: [distIndexPath], - cwd: packageRoot, - stderr: 'pipe', - }); - client = new Client({ name: 'input-schema-test', version: '0.0.0' }); - await client.connect(transport); - }); - - afterAll(async () => { - await client?.close(); - }); - - it('advertises nextThoughtNeeded as required', async () => { - const { tools } = await client.listTools(); - const tool = tools.find(t => t.name === 'sequentialthinking'); - expect(tool).toBeDefined(); - expect(tool!.inputSchema.required).toEqual( - expect.arrayContaining(['thought', 'nextThoughtNeeded', 'thoughtNumber', 'totalThoughts']) - ); - }); - - it('rejects a call that omits nextThoughtNeeded', async () => { - const result = await client.callTool({ - name: 'sequentialthinking', - arguments: { thought: 't', thoughtNumber: 1, totalThoughts: 1 }, - }); - expect(result.isError).toBe(true); - expect(JSON.stringify(result.content)).toMatch(/nextThoughtNeeded/); - }); - - it.each(['True', 'FALSE', 'true', 'false'])('accepts the string %s', async (value) => { - const result = await client.callTool({ - name: 'sequentialthinking', - arguments: { thought: 't', nextThoughtNeeded: value, thoughtNumber: 1, totalThoughts: 1 }, - }); - expect(result.isError).toBeFalsy(); - }); - - it.each(['yes', '', '1'])('rejects the string %j', async (value) => { - const result = await client.callTool({ - name: 'sequentialthinking', - arguments: { thought: 't', nextThoughtNeeded: value, thoughtNumber: 1, totalThoughts: 1 }, - }); - expect(result.isError).toBe(true); - }); -}); diff --git a/src/sequentialthinking/__tests__/lib.test.ts b/src/sequentialthinking/__tests__/lib.test.ts deleted file mode 100644 index 2114c5e..0000000 --- a/src/sequentialthinking/__tests__/lib.test.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import { SequentialThinkingServer, ThoughtData } from '../lib.js'; - -// Mock chalk to avoid ESM issues -vi.mock('chalk', () => { - const chalkMock = { - yellow: (str: string) => str, - green: (str: string) => str, - blue: (str: string) => str, - }; - return { - default: chalkMock, - }; -}); - -describe('SequentialThinkingServer', () => { - let server: SequentialThinkingServer; - - beforeEach(() => { - // Disable thought logging for tests - process.env.DISABLE_THOUGHT_LOGGING = 'true'; - server = new SequentialThinkingServer(); - }); - - // Note: Input validation tests removed - validation now happens at the tool - // registration layer via Zod schemas before processThought is called - - describe('processThought - valid inputs', () => { - it('should accept valid basic thought', () => { - const input = { - thought: 'This is my first thought', - thoughtNumber: 1, - totalThoughts: 3, - nextThoughtNeeded: true - }; - - const result = server.processThought(input); - expect(result.isError).toBeUndefined(); - - const data = JSON.parse(result.content[0].text); - expect(data.thoughtNumber).toBe(1); - expect(data.totalThoughts).toBe(3); - expect(data.nextThoughtNeeded).toBe(true); - expect(data.thoughtHistoryLength).toBe(1); - }); - - it('should accept thought with optional fields', () => { - const input = { - thought: 'Revising my earlier idea', - thoughtNumber: 2, - totalThoughts: 3, - nextThoughtNeeded: true, - isRevision: true, - revisesThought: 1, - needsMoreThoughts: false - }; - - const result = server.processThought(input); - expect(result.isError).toBeUndefined(); - - const data = JSON.parse(result.content[0].text); - expect(data.thoughtNumber).toBe(2); - expect(data.thoughtHistoryLength).toBe(1); - }); - - it('should track multiple thoughts in history', () => { - const input1 = { - thought: 'First thought', - thoughtNumber: 1, - totalThoughts: 3, - nextThoughtNeeded: true - }; - - const input2 = { - thought: 'Second thought', - thoughtNumber: 2, - totalThoughts: 3, - nextThoughtNeeded: true - }; - - const input3 = { - thought: 'Final thought', - thoughtNumber: 3, - totalThoughts: 3, - nextThoughtNeeded: false - }; - - server.processThought(input1); - server.processThought(input2); - const result = server.processThought(input3); - - const data = JSON.parse(result.content[0].text); - expect(data.thoughtHistoryLength).toBe(3); - expect(data.nextThoughtNeeded).toBe(false); - }); - - it('should auto-adjust totalThoughts if thoughtNumber exceeds it', () => { - const input = { - thought: 'Thought 5', - thoughtNumber: 5, - totalThoughts: 3, - nextThoughtNeeded: true - }; - - const result = server.processThought(input); - const data = JSON.parse(result.content[0].text); - - expect(data.totalThoughts).toBe(5); - }); - }); - - describe('processThought - branching', () => { - it('should track branches correctly', () => { - const input1 = { - thought: 'Main thought', - thoughtNumber: 1, - totalThoughts: 3, - nextThoughtNeeded: true - }; - - const input2 = { - thought: 'Branch A thought', - thoughtNumber: 2, - totalThoughts: 3, - nextThoughtNeeded: true, - branchFromThought: 1, - branchId: 'branch-a' - }; - - const input3 = { - thought: 'Branch B thought', - thoughtNumber: 2, - totalThoughts: 3, - nextThoughtNeeded: false, - branchFromThought: 1, - branchId: 'branch-b' - }; - - server.processThought(input1); - server.processThought(input2); - const result = server.processThought(input3); - - const data = JSON.parse(result.content[0].text); - expect(data.branches).toContain('branch-a'); - expect(data.branches).toContain('branch-b'); - expect(data.branches.length).toBe(2); - expect(data.thoughtHistoryLength).toBe(3); - }); - - it('should allow multiple thoughts in same branch', () => { - const input1 = { - thought: 'Branch thought 1', - thoughtNumber: 1, - totalThoughts: 2, - nextThoughtNeeded: true, - branchFromThought: 1, - branchId: 'branch-a' - }; - - const input2 = { - thought: 'Branch thought 2', - thoughtNumber: 2, - totalThoughts: 2, - nextThoughtNeeded: false, - branchFromThought: 1, - branchId: 'branch-a' - }; - - server.processThought(input1); - const result = server.processThought(input2); - - const data = JSON.parse(result.content[0].text); - expect(data.branches).toContain('branch-a'); - expect(data.branches.length).toBe(1); - }); - }); - - describe('processThought - edge cases', () => { - it('should handle very long thought strings', () => { - const input = { - thought: 'a'.repeat(10000), - thoughtNumber: 1, - totalThoughts: 1, - nextThoughtNeeded: false - }; - - const result = server.processThought(input); - expect(result.isError).toBeUndefined(); - }); - - it('should handle thoughtNumber = 1, totalThoughts = 1', () => { - const input = { - thought: 'Only thought', - thoughtNumber: 1, - totalThoughts: 1, - nextThoughtNeeded: false - }; - - const result = server.processThought(input); - expect(result.isError).toBeUndefined(); - - const data = JSON.parse(result.content[0].text); - expect(data.thoughtNumber).toBe(1); - expect(data.totalThoughts).toBe(1); - }); - - it('should handle nextThoughtNeeded = false', () => { - const input = { - thought: 'Final thought', - thoughtNumber: 3, - totalThoughts: 3, - nextThoughtNeeded: false - }; - - const result = server.processThought(input); - const data = JSON.parse(result.content[0].text); - - expect(data.nextThoughtNeeded).toBe(false); - }); - }); - - describe('processThought - response format', () => { - it('should return correct response structure on success', () => { - const input = { - thought: 'Test thought', - thoughtNumber: 1, - totalThoughts: 1, - nextThoughtNeeded: false - }; - - const result = server.processThought(input); - - expect(result).toHaveProperty('content'); - expect(Array.isArray(result.content)).toBe(true); - expect(result.content.length).toBe(1); - expect(result.content[0]).toHaveProperty('type', 'text'); - expect(result.content[0]).toHaveProperty('text'); - }); - - it('should return valid JSON in response', () => { - const input = { - thought: 'Test thought', - thoughtNumber: 1, - totalThoughts: 1, - nextThoughtNeeded: false - }; - - const result = server.processThought(input); - - expect(() => JSON.parse(result.content[0].text)).not.toThrow(); - }); - }); - - describe('processThought - with logging enabled', () => { - let serverWithLogging: SequentialThinkingServer; - - beforeEach(() => { - // Enable thought logging for these tests - delete process.env.DISABLE_THOUGHT_LOGGING; - serverWithLogging = new SequentialThinkingServer(); - }); - - afterEach(() => { - // Reset to disabled for other tests - process.env.DISABLE_THOUGHT_LOGGING = 'true'; - }); - - it('should format and log regular thoughts', () => { - const input = { - thought: 'Test thought with logging', - thoughtNumber: 1, - totalThoughts: 3, - nextThoughtNeeded: true - }; - - const result = serverWithLogging.processThought(input); - expect(result.isError).toBeUndefined(); - }); - - it('should format and log revision thoughts', () => { - const input = { - thought: 'Revised thought', - thoughtNumber: 2, - totalThoughts: 3, - nextThoughtNeeded: true, - isRevision: true, - revisesThought: 1 - }; - - const result = serverWithLogging.processThought(input); - expect(result.isError).toBeUndefined(); - }); - - it('should format and log branch thoughts', () => { - const input = { - thought: 'Branch thought', - thoughtNumber: 2, - totalThoughts: 3, - nextThoughtNeeded: false, - branchFromThought: 1, - branchId: 'branch-a' - }; - - const result = serverWithLogging.processThought(input); - expect(result.isError).toBeUndefined(); - }); - }); -}); diff --git a/src/sequentialthinking/__tests__/server-version.test.ts b/src/sequentialthinking/__tests__/server-version.test.ts deleted file mode 100644 index c7d7f5c..0000000 --- a/src/sequentialthinking/__tests__/server-version.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { createRequire } from 'node:module'; -import { existsSync } from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; -import { resolvePackageVersion, SERVER_VERSION } from '../version.js'; - -const packageJson = createRequire(import.meta.url)('../package.json') as { version: string }; -const packageRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); -const distVersionPath = path.join(packageRoot, 'dist', 'version.js'); -const distIndexPath = path.join(packageRoot, 'dist', 'index.js'); - -describe('server version', () => { - it('uses package.json version instead of a hardcoded string', () => { - expect(SERVER_VERSION).toBe(packageJson.version); - expect(resolvePackageVersion()).toBe(packageJson.version); - expect(SERVER_VERSION).not.toBe('0.2.0'); - }); - - // CI runs `npm test` before the dedicated build job. `npm ci` usually - // materializes dist/ via prepare, but that is not guaranteed (e.g. local - // `rm -rf dist && npm test`, or install with --ignore-scripts). - it.skipIf(!existsSync(distVersionPath))( - 'resolves package.json from the dist layout after build', - async () => { - const distModule = (await import(pathToFileURL(distVersionPath).href)) as { - SERVER_VERSION: string; - }; - expect(distModule.SERVER_VERSION).toBe(packageJson.version); - }, - ); - - it.skipIf(!existsSync(distIndexPath))( - 'stdio initialize reports package.json version in serverInfo', - async () => { - const transport = new StdioClientTransport({ - command: process.execPath, - args: [distIndexPath], - cwd: packageRoot, - stderr: 'pipe', - }); - const client = new Client({ name: 'version-smoke', version: '0.0.0' }); - - try { - await client.connect(transport); - const serverInfo = client.getServerVersion(); - expect(serverInfo?.name).toBe('sequential-thinking-server'); - expect(serverInfo?.version).toBe(packageJson.version); - expect(serverInfo?.version).not.toBe('0.2.0'); - } finally { - await client.close(); - } - }, - ); -}); diff --git a/src/sequentialthinking/index.ts b/src/sequentialthinking/index.ts deleted file mode 100644 index 1ae09d1..0000000 --- a/src/sequentialthinking/index.ts +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env node - -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; -import { SequentialThinkingServer } from './lib.js'; -import { SERVER_VERSION } from './version.js'; - -/** Safe boolean coercion that correctly handles string "false". A union+transform, - * not z.preprocess (whose input type is `unknown`), so toJSONSchema keeps this required. */ -const coercedBoolean = z.union([z.boolean(), z.string()]).transform((val, ctx) => { - if (typeof val === "boolean") return val; - if (val.toLowerCase() === "true") return true; - if (val.toLowerCase() === "false") return false; - ctx.addIssue({ code: "custom", message: `Expected boolean or "true"/"false" string, received "${val}"` }); - return z.NEVER; -}); - -const server = new McpServer({ - name: "sequential-thinking-server", - version: SERVER_VERSION, -}); - -const thinkingServer = new SequentialThinkingServer(); - -server.registerTool( - "sequentialthinking", - { - title: "Sequential Thinking", - description: `A detailed tool for dynamic and reflective problem-solving through thoughts. -This tool helps analyze problems through a flexible thinking process that can adapt and evolve. -Each thought can build on, question, or revise previous insights as understanding deepens. - -When to use this tool: -- Breaking down complex problems into steps -- Planning and design with room for revision -- Analysis that might need course correction -- Problems where the full scope might not be clear initially -- Problems that require a multi-step solution -- Tasks that need to maintain context over multiple steps -- Situations where irrelevant information needs to be filtered out - -Key features: -- You can adjust total_thoughts up or down as you progress -- You can question or revise previous thoughts -- You can add more thoughts even after reaching what seemed like the end -- You can express uncertainty and explore alternative approaches -- Not every thought needs to build linearly - you can branch or backtrack -- Generates a solution hypothesis -- Verifies the hypothesis based on the Chain of Thought steps -- Repeats the process until satisfied -- Provides a correct answer - -Parameters explained: -- thought: Your current thinking step, which can include: - * Regular analytical steps - * Revisions of previous thoughts - * Questions about previous decisions - * Realizations about needing more analysis - * Changes in approach - * Hypothesis generation - * Hypothesis verification -- nextThoughtNeeded: True if you need more thinking, even if at what seemed like the end -- thoughtNumber: Current number in sequence (can go beyond initial total if needed) -- totalThoughts: Current estimate of thoughts needed (can be adjusted up/down) -- isRevision: A boolean indicating if this thought revises previous thinking -- revisesThought: If is_revision is true, which thought number is being reconsidered -- branchFromThought: If branching, which thought number is the branching point -- branchId: Identifier for the current branch (if any) -- needsMoreThoughts: If reaching end but realizing more thoughts needed - -You should: -1. Start with an initial estimate of needed thoughts, but be ready to adjust -2. Feel free to question or revise previous thoughts -3. Don't hesitate to add more thoughts if needed, even at the "end" -4. Express uncertainty when present -5. Mark thoughts that revise previous thinking or branch into new paths -6. Ignore information that is irrelevant to the current step -7. Generate a solution hypothesis when appropriate -8. Verify the hypothesis based on the Chain of Thought steps -9. Repeat the process until satisfied with the solution -10. Provide a single, ideally correct answer as the final output -11. Only set nextThoughtNeeded to false when truly done and a satisfactory answer is reached`, - inputSchema: { - thought: z.string().describe("Your current thinking step"), - nextThoughtNeeded: coercedBoolean.describe("Whether another thought step is needed"), - thoughtNumber: z.coerce.number().int().min(1).describe("Current thought number (numeric value, e.g., 1, 2, 3)"), - totalThoughts: z.coerce.number().int().min(1).describe("Estimated total thoughts needed (numeric value, e.g., 5, 10)"), - isRevision: coercedBoolean.optional().describe("Whether this revises previous thinking"), - revisesThought: z.coerce.number().int().min(1).optional().describe("Which thought is being reconsidered"), - branchFromThought: z.coerce.number().int().min(1).optional().describe("Branching point thought number"), - branchId: z.string().optional().describe("Branch identifier"), - needsMoreThoughts: coercedBoolean.optional().describe("If more thoughts are needed") - }, - annotations: { - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - outputSchema: { - thoughtNumber: z.number(), - totalThoughts: z.number(), - nextThoughtNeeded: z.boolean(), - branches: z.array(z.string()), - thoughtHistoryLength: z.number() - }, - }, - async (args) => { - const result = thinkingServer.processThought(args); - - if (result.isError) { - return result; - } - - // Parse the JSON response to get structured content - const parsedContent = JSON.parse(result.content[0].text); - - return { - content: result.content, - structuredContent: parsedContent - }; - } -); - -async function runServer() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Sequential Thinking MCP Server running on stdio"); -} - -runServer().catch((error) => { - console.error("Fatal error running server:", error); - process.exit(1); -}); diff --git a/src/sequentialthinking/lib.ts b/src/sequentialthinking/lib.ts deleted file mode 100644 index 31a1098..0000000 --- a/src/sequentialthinking/lib.ts +++ /dev/null @@ -1,99 +0,0 @@ -import chalk from 'chalk'; - -export interface ThoughtData { - thought: string; - thoughtNumber: number; - totalThoughts: number; - isRevision?: boolean; - revisesThought?: number; - branchFromThought?: number; - branchId?: string; - needsMoreThoughts?: boolean; - nextThoughtNeeded: boolean; -} - -export class SequentialThinkingServer { - private thoughtHistory: ThoughtData[] = []; - private branches: Record = {}; - private disableThoughtLogging: boolean; - - constructor() { - this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true"; - } - - private formatThought(thoughtData: ThoughtData): string { - const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId } = thoughtData; - - let prefix = ''; - let context = ''; - - if (isRevision) { - prefix = chalk.yellow('๐Ÿ”„ Revision'); - context = ` (revising thought ${revisesThought})`; - } else if (branchFromThought) { - prefix = chalk.green('๐ŸŒฟ Branch'); - context = ` (from thought ${branchFromThought}, ID: ${branchId})`; - } else { - prefix = chalk.blue('๐Ÿ’ญ Thought'); - context = ''; - } - - const header = `${prefix} ${thoughtNumber}/${totalThoughts}${context}`; - const border = 'โ”€'.repeat(Math.max(header.length, thought.length) + 4); - - return ` -โ”Œ${border}โ” -โ”‚ ${header} โ”‚ -โ”œ${border}โ”ค -โ”‚ ${thought.padEnd(border.length - 2)} โ”‚ -โ””${border}โ”˜`; - } - - public processThought(input: ThoughtData): { content: Array<{ type: "text"; text: string }>; isError?: boolean } { - try { - // Validation happens at the tool registration layer via Zod - // Adjust totalThoughts if thoughtNumber exceeds it - if (input.thoughtNumber > input.totalThoughts) { - input.totalThoughts = input.thoughtNumber; - } - - this.thoughtHistory.push(input); - - if (input.branchFromThought && input.branchId) { - if (!this.branches[input.branchId]) { - this.branches[input.branchId] = []; - } - this.branches[input.branchId].push(input); - } - - if (!this.disableThoughtLogging) { - const formattedThought = this.formatThought(input); - console.error(formattedThought); - } - - return { - content: [{ - type: "text" as const, - text: JSON.stringify({ - thoughtNumber: input.thoughtNumber, - totalThoughts: input.totalThoughts, - nextThoughtNeeded: input.nextThoughtNeeded, - branches: Object.keys(this.branches), - thoughtHistoryLength: this.thoughtHistory.length - }, null, 2) - }] - }; - } catch (error) { - return { - content: [{ - type: "text" as const, - text: JSON.stringify({ - error: error instanceof Error ? error.message : String(error), - status: 'failed' - }, null, 2) - }], - isError: true - }; - } - } -} diff --git a/src/sequentialthinking/package.json b/src/sequentialthinking/package.json deleted file mode 100644 index a668a47..0000000 --- a/src/sequentialthinking/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "@modelcontextprotocol/server-sequential-thinking", - "version": "0.6.2", - "description": "MCP server for sequential thinking and problem solving", - "license": "SEE LICENSE IN LICENSE", - "mcpName": "io.github.modelcontextprotocol/server-sequential-thinking", - "author": "Model Context Protocol a Series of LF Projects, LLC.", - "homepage": "https://modelcontextprotocol.io", - "bugs": "https://github.com/modelcontextprotocol/servers/issues", - "repository": { - "type": "git", - "url": "https://github.com/modelcontextprotocol/servers.git" - }, - "type": "module", - "bin": { - "mcp-server-sequential-thinking": "dist/index.js" - }, - "files": [ - "dist" - ], - "scripts": { - "build": "tsc && shx chmod +x dist/*.js", - "prepare": "npm run build", - "watch": "tsc --watch", - "test": "vitest run --coverage" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.30.0", - "chalk": "^5.3.0", - "yargs": "^17.7.2", - "zod": "^4.0.0" - }, - "devDependencies": { - "@types/node": "^22", - "@types/yargs": "^17.0.32", - "@vitest/coverage-v8": "^4.1.8", - "shx": "^0.4.0", - "typescript": "^5.3.3", - "vitest": "^4.1.8" - } -} diff --git a/src/sequentialthinking/version.ts b/src/sequentialthinking/version.ts deleted file mode 100644 index 1ab7d8f..0000000 --- a/src/sequentialthinking/version.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { createRequire } from 'node:module'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -/** - * Resolve this package's version from package.json. - * - * Works both from source (`src/sequentialthinking/`) and from the published - * layout (`dist/`), where package.json lives one directory up. - */ -export function resolvePackageVersion(): string { - const require = createRequire(import.meta.url); - const moduleDir = path.dirname(fileURLToPath(import.meta.url)); - const candidates = [ - path.join(moduleDir, 'package.json'), - path.join(moduleDir, '..', 'package.json'), - ]; - - for (const candidate of candidates) { - try { - const pkg = require(candidate) as { version?: string }; - if (pkg.version) { - return pkg.version; - } - } catch { - // Try the next candidate when running from dist/ or source. - } - } - - throw new Error('Could not locate package.json for server version'); -} - -export const SERVER_VERSION = resolvePackageVersion(); diff --git a/src/sequentialthinking/vitest.config.ts b/src/sequentialthinking/vitest.config.ts deleted file mode 100644 index d414ec8..0000000 --- a/src/sequentialthinking/vitest.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - environment: 'node', - include: ['**/__tests__/**/*.test.ts'], - coverage: { - provider: 'v8', - include: ['**/*.ts'], - exclude: ['**/__tests__/**', '**/dist/**'], - }, - }, -}); diff --git a/src/slack/README.md b/src/slack/README.md new file mode 100644 index 0000000..b3d80a2 --- /dev/null +++ b/src/slack/README.md @@ -0,0 +1,120 @@ +# Slack MCP Server + +MCP Server for the Slack API, enabling Claude to interact with Slack workspaces. + +## Tools + +1. `slack_list_channels` + - List public channels in the workspace + - Optional inputs: + - `limit` (number, default: 100, max: 200): Maximum number of channels to return + - `cursor` (string): Pagination cursor for next page + - Returns: List of channels with their IDs and information + +2. `slack_post_message` + - Post a new message to a Slack channel + - Required inputs: + - `channel_id` (string): The ID of the channel to post to + - `text` (string): The message text to post + - Returns: Message posting confirmation and timestamp + +3. `slack_reply_to_thread` + - Reply to a specific message thread + - Required inputs: + - `channel_id` (string): The channel containing the thread + - `thread_ts` (string): Timestamp of the parent message + - `text` (string): The reply text + - Returns: Reply confirmation and timestamp + +4. `slack_add_reaction` + - Add an emoji reaction to a message + - Required inputs: + - `channel_id` (string): The channel containing the message + - `timestamp` (string): Message timestamp to react to + - `reaction` (string): Emoji name without colons + - Returns: Reaction confirmation + +5. `slack_get_channel_history` + - Get recent messages from a channel + - Required inputs: + - `channel_id` (string): The channel ID + - Optional inputs: + - `limit` (number, default: 10): Number of messages to retrieve + - Returns: List of messages with their content and metadata + +6. `slack_get_thread_replies` + - Get all replies in a message thread + - Required inputs: + - `channel_id` (string): The channel containing the thread + - `thread_ts` (string): Timestamp of the parent message + - Returns: List of replies with their content and metadata + + +7. `slack_get_users` + - Get list of workspace users with basic profile information + - Optional inputs: + - `cursor` (string): Pagination cursor for next page + - `limit` (number, default: 100, max: 200): Maximum users to return + - Returns: List of users with their basic profiles + +8. `slack_get_user_profile` + - Get detailed profile information for a specific user + - Required inputs: + - `user_id` (string): The user's ID + - Returns: Detailed user profile information + +## Setup + +1. Create a Slack App: + - Visit the [Slack Apps page](https://api.slack.com/apps) + - Click "Create New App" + - Choose "From scratch" + - Name your app and select your workspace + +2. Configure Bot Token Scopes: + Navigate to "OAuth & Permissions" and add these scopes: + - `channels:history` - View messages and other content in public channels + - `channels:read` - View basic channel information + - `chat:write` - Send messages as the app + - `reactions:write` - Add emoji reactions to messages + - `users:read` - View users and their basic information + +4. Install App to Workspace: + - Click "Install to Workspace" and authorize the app + - Save the "Bot User OAuth Token" that starts with `xoxb-` + +5. Get your Team ID (starts with a `T`) by following [this guidance](https://slack.com/help/articles/221769328-Locate-your-Slack-URL-or-ID#find-your-workspace-or-org-id) + +### Usage with Claude Desktop + +Add the following to your `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "slack": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-slack" + ], + "env": { + "SLACK_BOT_TOKEN": "xoxb-your-bot-token", + "SLACK_TEAM_ID": "T01234567" + } + } + } +} +``` + +### Troubleshooting + +If you encounter permission errors, verify that: +1. All required scopes are added to your Slack app +2. The app is properly installed to your workspace +3. The tokens and workspace ID are correctly copied to your configuration +4. The app has been added to the channels it needs to access + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/slack/index.ts b/src/slack/index.ts new file mode 100644 index 0000000..8cdc143 --- /dev/null +++ b/src/slack/index.ts @@ -0,0 +1,554 @@ +#!/usr/bin/env node +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequest, + CallToolRequestSchema, + ListToolsRequestSchema, + Tool, +} from "@modelcontextprotocol/sdk/types.js"; + +// Type definitions for tool arguments +interface ListChannelsArgs { + limit?: number; + cursor?: string; +} + +interface PostMessageArgs { + channel_id: string; + text: string; +} + +interface ReplyToThreadArgs { + channel_id: string; + thread_ts: string; + text: string; +} + +interface AddReactionArgs { + channel_id: string; + timestamp: string; + reaction: string; +} + +interface GetChannelHistoryArgs { + channel_id: string; + limit?: number; +} + +interface GetThreadRepliesArgs { + channel_id: string; + thread_ts: string; +} + +interface GetUsersArgs { + cursor?: string; + limit?: number; +} + +interface GetUserProfileArgs { + user_id: string; +} + +// Tool definitions +const listChannelsTool: Tool = { + name: "slack_list_channels", + description: "List public channels in the workspace with pagination", + inputSchema: { + type: "object", + properties: { + limit: { + type: "number", + description: + "Maximum number of channels to return (default 100, max 200)", + default: 100, + }, + cursor: { + type: "string", + description: "Pagination cursor for next page of results", + }, + }, + }, +}; + +const postMessageTool: Tool = { + name: "slack_post_message", + description: "Post a new message to a Slack channel", + inputSchema: { + type: "object", + properties: { + channel_id: { + type: "string", + description: "The ID of the channel to post to", + }, + text: { + type: "string", + description: "The message text to post", + }, + }, + required: ["channel_id", "text"], + }, +}; + +const replyToThreadTool: Tool = { + name: "slack_reply_to_thread", + description: "Reply to a specific message thread in Slack", + inputSchema: { + type: "object", + properties: { + channel_id: { + type: "string", + description: "The ID of the channel containing the thread", + }, + thread_ts: { + type: "string", + description: "The timestamp of the parent message", + }, + text: { + type: "string", + description: "The reply text", + }, + }, + required: ["channel_id", "thread_ts", "text"], + }, +}; + +const addReactionTool: Tool = { + name: "slack_add_reaction", + description: "Add a reaction emoji to a message", + inputSchema: { + type: "object", + properties: { + channel_id: { + type: "string", + description: "The ID of the channel containing the message", + }, + timestamp: { + type: "string", + description: "The timestamp of the message to react to", + }, + reaction: { + type: "string", + description: "The name of the emoji reaction (without ::)", + }, + }, + required: ["channel_id", "timestamp", "reaction"], + }, +}; + +const getChannelHistoryTool: Tool = { + name: "slack_get_channel_history", + description: "Get recent messages from a channel", + inputSchema: { + type: "object", + properties: { + channel_id: { + type: "string", + description: "The ID of the channel", + }, + limit: { + type: "number", + description: "Number of messages to retrieve (default 10)", + default: 10, + }, + }, + required: ["channel_id"], + }, +}; + +const getThreadRepliesTool: Tool = { + name: "slack_get_thread_replies", + description: "Get all replies in a message thread", + inputSchema: { + type: "object", + properties: { + channel_id: { + type: "string", + description: "The ID of the channel containing the thread", + }, + thread_ts: { + type: "string", + description: "The timestamp of the parent message", + }, + }, + required: ["channel_id", "thread_ts"], + }, +}; + +const getUsersTool: Tool = { + name: "slack_get_users", + description: + "Get a list of all users in the workspace with their basic profile information", + inputSchema: { + type: "object", + properties: { + cursor: { + type: "string", + description: "Pagination cursor for next page of results", + }, + limit: { + type: "number", + description: "Maximum number of users to return (default 100, max 200)", + default: 100, + }, + }, + }, +}; + +const getUserProfileTool: Tool = { + name: "slack_get_user_profile", + description: "Get detailed profile information for a specific user", + inputSchema: { + type: "object", + properties: { + user_id: { + type: "string", + description: "The ID of the user", + }, + }, + required: ["user_id"], + }, +}; + +class SlackClient { + private botHeaders: { Authorization: string; "Content-Type": string }; + + constructor(botToken: string) { + this.botHeaders = { + Authorization: `Bearer ${botToken}`, + "Content-Type": "application/json", + }; + } + + async getChannels(limit: number = 100, cursor?: string): Promise { + const params = new URLSearchParams({ + types: "public_channel", + exclude_archived: "true", + limit: Math.min(limit, 200).toString(), + team_id: process.env.SLACK_TEAM_ID!, + }); + + if (cursor) { + params.append("cursor", cursor); + } + + const response = await fetch( + `https://slack.com/api/conversations.list?${params}`, + { headers: this.botHeaders }, + ); + + return response.json(); + } + + async postMessage(channel_id: string, text: string): Promise { + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: this.botHeaders, + body: JSON.stringify({ + channel: channel_id, + text: text, + }), + }); + + return response.json(); + } + + async postReply( + channel_id: string, + thread_ts: string, + text: string, + ): Promise { + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: this.botHeaders, + body: JSON.stringify({ + channel: channel_id, + thread_ts: thread_ts, + text: text, + }), + }); + + return response.json(); + } + + async addReaction( + channel_id: string, + timestamp: string, + reaction: string, + ): Promise { + const response = await fetch("https://slack.com/api/reactions.add", { + method: "POST", + headers: this.botHeaders, + body: JSON.stringify({ + channel: channel_id, + timestamp: timestamp, + name: reaction, + }), + }); + + return response.json(); + } + + async getChannelHistory( + channel_id: string, + limit: number = 10, + ): Promise { + const params = new URLSearchParams({ + channel: channel_id, + limit: limit.toString(), + }); + + const response = await fetch( + `https://slack.com/api/conversations.history?${params}`, + { headers: this.botHeaders }, + ); + + return response.json(); + } + + async getThreadReplies(channel_id: string, thread_ts: string): Promise { + const params = new URLSearchParams({ + channel: channel_id, + ts: thread_ts, + }); + + const response = await fetch( + `https://slack.com/api/conversations.replies?${params}`, + { headers: this.botHeaders }, + ); + + return response.json(); + } + + async getUsers(limit: number = 100, cursor?: string): Promise { + const params = new URLSearchParams({ + limit: Math.min(limit, 200).toString(), + team_id: process.env.SLACK_TEAM_ID!, + }); + + if (cursor) { + params.append("cursor", cursor); + } + + const response = await fetch(`https://slack.com/api/users.list?${params}`, { + headers: this.botHeaders, + }); + + return response.json(); + } + + async getUserProfile(user_id: string): Promise { + const params = new URLSearchParams({ + user: user_id, + include_labels: "true", + }); + + const response = await fetch( + `https://slack.com/api/users.profile.get?${params}`, + { headers: this.botHeaders }, + ); + + return response.json(); + } +} + +async function main() { + const botToken = process.env.SLACK_BOT_TOKEN; + const teamId = process.env.SLACK_TEAM_ID; + + if (!botToken || !teamId) { + console.error( + "Please set SLACK_BOT_TOKEN and SLACK_TEAM_ID environment variables", + ); + process.exit(1); + } + + console.error("Starting Slack MCP Server..."); + const server = new Server( + { + name: "Slack MCP Server", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + }, + ); + + const slackClient = new SlackClient(botToken); + + server.setRequestHandler( + CallToolRequestSchema, + async (request: CallToolRequest) => { + console.error("Received CallToolRequest:", request); + try { + if (!request.params.arguments) { + throw new Error("No arguments provided"); + } + + switch (request.params.name) { + case "slack_list_channels": { + const args = request.params + .arguments as unknown as ListChannelsArgs; + const response = await slackClient.getChannels( + args.limit, + args.cursor, + ); + return { + content: [{ type: "text", text: JSON.stringify(response) }], + }; + } + + case "slack_post_message": { + const args = request.params.arguments as unknown as PostMessageArgs; + if (!args.channel_id || !args.text) { + throw new Error( + "Missing required arguments: channel_id and text", + ); + } + const response = await slackClient.postMessage( + args.channel_id, + args.text, + ); + return { + content: [{ type: "text", text: JSON.stringify(response) }], + }; + } + + case "slack_reply_to_thread": { + const args = request.params + .arguments as unknown as ReplyToThreadArgs; + if (!args.channel_id || !args.thread_ts || !args.text) { + throw new Error( + "Missing required arguments: channel_id, thread_ts, and text", + ); + } + const response = await slackClient.postReply( + args.channel_id, + args.thread_ts, + args.text, + ); + return { + content: [{ type: "text", text: JSON.stringify(response) }], + }; + } + + case "slack_add_reaction": { + const args = request.params.arguments as unknown as AddReactionArgs; + if (!args.channel_id || !args.timestamp || !args.reaction) { + throw new Error( + "Missing required arguments: channel_id, timestamp, and reaction", + ); + } + const response = await slackClient.addReaction( + args.channel_id, + args.timestamp, + args.reaction, + ); + return { + content: [{ type: "text", text: JSON.stringify(response) }], + }; + } + + case "slack_get_channel_history": { + const args = request.params + .arguments as unknown as GetChannelHistoryArgs; + if (!args.channel_id) { + throw new Error("Missing required argument: channel_id"); + } + const response = await slackClient.getChannelHistory( + args.channel_id, + args.limit, + ); + return { + content: [{ type: "text", text: JSON.stringify(response) }], + }; + } + + case "slack_get_thread_replies": { + const args = request.params + .arguments as unknown as GetThreadRepliesArgs; + if (!args.channel_id || !args.thread_ts) { + throw new Error( + "Missing required arguments: channel_id and thread_ts", + ); + } + const response = await slackClient.getThreadReplies( + args.channel_id, + args.thread_ts, + ); + return { + content: [{ type: "text", text: JSON.stringify(response) }], + }; + } + + case "slack_get_users": { + const args = request.params.arguments as unknown as GetUsersArgs; + const response = await slackClient.getUsers( + args.limit, + args.cursor, + ); + return { + content: [{ type: "text", text: JSON.stringify(response) }], + }; + } + + case "slack_get_user_profile": { + const args = request.params + .arguments as unknown as GetUserProfileArgs; + if (!args.user_id) { + throw new Error("Missing required argument: user_id"); + } + const response = await slackClient.getUserProfile(args.user_id); + return { + content: [{ type: "text", text: JSON.stringify(response) }], + }; + } + + default: + throw new Error(`Unknown tool: ${request.params.name}`); + } + } catch (error) { + console.error("Error executing tool:", error); + return { + content: [ + { + type: "text", + text: JSON.stringify({ + error: error instanceof Error ? error.message : String(error), + }), + }, + ], + }; + } + }, + ); + + server.setRequestHandler(ListToolsRequestSchema, async () => { + console.error("Received ListToolsRequest"); + return { + tools: [ + listChannelsTool, + postMessageTool, + replyToThreadTool, + addReactionTool, + getChannelHistoryTool, + getThreadRepliesTool, + getUsersTool, + getUserProfileTool, + ], + }; + }); + + const transport = new StdioServerTransport(); + console.error("Connecting server to transport..."); + await server.connect(transport); + + console.error("Slack MCP Server running on stdio"); +} + +main().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); +}); diff --git a/src/slack/package.json b/src/slack/package.json new file mode 100644 index 0000000..8c8dcde --- /dev/null +++ b/src/slack/package.json @@ -0,0 +1,29 @@ +{ + "name": "@modelcontextprotocol/server-slack", + "version": "0.5.1", + "description": "MCP server for interacting with Slack", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-slack": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0" + }, + "devDependencies": { + "@types/node": "^22.9.3", + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/slack/tsconfig.json b/src/slack/tsconfig.json new file mode 100644 index 0000000..4d33cae --- /dev/null +++ b/src/slack/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] + } + \ No newline at end of file diff --git a/src/sqlite/.python-version b/src/sqlite/.python-version new file mode 100644 index 0000000..c8cfe39 --- /dev/null +++ b/src/sqlite/.python-version @@ -0,0 +1 @@ +3.10 diff --git a/src/sqlite/README.md b/src/sqlite/README.md new file mode 100644 index 0000000..5f211c4 --- /dev/null +++ b/src/sqlite/README.md @@ -0,0 +1,85 @@ +# SQLite MCP Server + +## Overview +A Model Context Protocol (MCP) server implementation that provides database interaction and business intelligence capabilities through SQLite. This server enables running SQL queries, analyzing business data, and automatically generating business insight memos. + +## Components + +### Resources +The server exposes a single dynamic resource: +- `memo://insights`: A continuously updated business insights memo that aggregates discovered insights during analysis + - Auto-updates as new insights are discovered via the append-insight tool + +### Prompts +The server provides a demonstration prompt: +- `mcp-demo`: Interactive prompt that guides users through database operations + - Required argument: `topic` - The business domain to analyze + - Generates appropriate database schemas and sample data + - Guides users through analysis and insight generation + - Integrates with the business insights memo + +### Tools +The server offers six core tools: + +#### Query Tools +- `read-query` + - Execute SELECT queries to read data from the database + - Input: + - `query` (string): The SELECT SQL query to execute + - Returns: Query results as array of objects + +- `write-query` + - Execute INSERT, UPDATE, or DELETE queries + - Input: + - `query` (string): The SQL modification query + - Returns: `{ affected_rows: number }` + +- `create-table` + - Create new tables in the database + - Input: + - `query` (string): CREATE TABLE SQL statement + - Returns: Confirmation of table creation + +#### Schema Tools +- `list-tables` + - Get a list of all tables in the database + - No input required + - Returns: Array of table names + +- `describe-table` + - View schema information for a specific table + - Input: + - `table_name` (string): Name of table to describe + - Returns: Array of column definitions with names and types + +#### Analysis Tools +- `append-insight` + - Add new business insights to the memo resource + - Input: + - `insight` (string): Business insight discovered from data analysis + - Returns: Confirmation of insight addition + - Triggers update of memo://insights resource + + +## Usage with Claude Desktop + +```bash +# Add the server to your claude_desktop_config.json +"mcpServers": { + "sqlite": { + "command": "uv", + "args": [ + "--directory", + "parent_of_servers_repo/servers/src/sqlite", + "run", + "mcp-server-sqlite", + "--db-path", + "~/test.db" + ] + } +} +``` + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. diff --git a/src/sqlite/pyproject.toml b/src/sqlite/pyproject.toml new file mode 100644 index 0000000..f09a6f0 --- /dev/null +++ b/src/sqlite/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "mcp-server-sqlite" +version = "0.6.1" +description = "A simple SQLite MCP server" +readme = "README.md" +requires-python = ">=3.10" +dependencies = ["mcp>=1.0.0"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.uv] +dev-dependencies = ["pyright>=1.1.389"] + +[project.scripts] +mcp-server-sqlite = "mcp_server_sqlite:main" diff --git a/src/sqlite/src/mcp_server_sqlite/__init__.py b/src/sqlite/src/mcp_server_sqlite/__init__.py new file mode 100644 index 0000000..1dc9f00 --- /dev/null +++ b/src/sqlite/src/mcp_server_sqlite/__init__.py @@ -0,0 +1,18 @@ +from . import server +import asyncio +import argparse + + +def main(): + """Main entry point for the package.""" + parser = argparse.ArgumentParser(description='SQLite MCP Server') + parser.add_argument('--db-path', + default="./sqlite_mcp_server.db", + help='Path to SQLite database file') + + args = parser.parse_args() + asyncio.run(server.main(args.db_path)) + + +# Optionally expose other important items at package level +__all__ = ["main", "server"] diff --git a/src/sqlite/src/mcp_server_sqlite/server.py b/src/sqlite/src/mcp_server_sqlite/server.py new file mode 100644 index 0000000..1b97a6a --- /dev/null +++ b/src/sqlite/src/mcp_server_sqlite/server.py @@ -0,0 +1,374 @@ +import sqlite3 +import logging +from contextlib import closing +from pathlib import Path +from mcp.server.models import InitializationOptions +import mcp.types as types +from mcp.server import NotificationOptions, Server +import mcp.server.stdio +from pydantic import AnyUrl +from typing import Any + +logger = logging.getLogger('mcp_sqlite_server') +logger.info("Starting MCP SQLite Server") + +PROMPT_TEMPLATE = """ +The assistants goal is to walkthrough an informative demo of MCP. To demonstrate the Model Context Protocol (MCP) we will leverage this example server to interact with an SQLite database. +It is important that you first explain to the user what is going on. The user has downloaded and installed the SQLite MCP Server and is now ready to use it. +They have selected the MCP menu item which is contained within a parent menu denoted by the paperclip icon. Inside this menu they selected an icon that illustrates two electrical plugs connecting. This is the MCP menu. +Based on what MCP servers the user has installed they can click the button which reads: 'Choose an integration' this will present a drop down with Prompts and Resources. The user has selected the prompt titled: 'mcp-demo'. +This text file is that prompt. The goal of the following instructions is to walk the user through the process of using the 3 core aspects of an MCP server. These are: Prompts, Tools, and Resources. +They have already used a prompt and provided a topic. The topic is: {topic}. The user is now ready to begin the demo. +Here is some more information about mcp and this specific mcp server: + +Prompts: +This server provides a pre-written prompt called "mcp-demo" that helps users create and analyze database scenarios. The prompt accepts a "topic" argument and guides users through creating tables, analyzing data, and generating insights. For example, if a user provides "retail sales" as the topic, the prompt will help create relevant database tables and guide the analysis process. Prompts basically serve as interactive templates that help structure the conversation with the LLM in a useful way. +Resources: +This server exposes one key resource: "memo://insights", which is a business insights memo that gets automatically updated throughout the analysis process. As users analyze the database and discover insights, the memo resource gets updated in real-time to reflect new findings. Resources act as living documents that provide context to the conversation. +Tools: +This server provides several SQL-related tools: +"read-query": Executes SELECT queries to read data from the database +"write-query": Executes INSERT, UPDATE, or DELETE queries to modify data +"create-table": Creates new tables in the database +"list-tables": Shows all existing tables +"describe-table": Shows the schema for a specific table +"append-insight": Adds a new business insight to the memo resource + + +You are an AI assistant tasked with generating a comprehensive business scenario based on a given topic. +Your goal is to create a narrative that involves a data-driven business problem, develop a database structure to support it, generate relevant queries, create a dashboard, and provide a final solution. + +At each step you will pause for user input to guide the scenario creation process. Overall ensure the scenario is engaging, informative, and demonstrates the capabilities of the SQLite MCP Server. +You should guide the scenario to completion. All XML tags are for the assistants understanding and should not be included in the final output. + +1. The user has chosen the topic: {topic}. + +2. Create a business problem narrative: +a. Describe a high-level business situation or problem based on the given topic. +b. Include a protagonist (the user) who needs to collect and analyze data from a database. +c. Add an external, potentially comedic reason why the data hasn't been prepared yet. +d. Mention an approaching deadline and the need to use Claude (you) as a business tool to help. + +3. Setup the data: +a. Instead of asking about the data that is required for the scenario, just go ahead and use the tools to create the data. Inform the user you are "Setting up the data". +b. Design a set of table schemas that represent the data needed for the business problem. +c. Include at least 2-3 tables with appropriate columns and data types. +d. Leverage the tools to create the tables in the SQLite database. +e. Create INSERT statements to populate each table with relevant synthetic data. +f. Ensure the data is diverse and representative of the business problem. +g. Include at least 10-15 rows of data for each table. + +4. Pause for user input: +a. Summarize to the user what data we have created. +b. Present the user with a set of multiple choices for the next steps. +c. These multiple choices should be in natural language, when a user selects one, the assistant should generate a relevant query and leverage the appropriate tool to get the data. + +6. Iterate on queries: +a. Present 1 additional multiple-choice query options to the user. Its important to not loop too many times as this is a short demo. +b. Explain the purpose of each query option. +c. Wait for the user to select one of the query options. +d. After each query be sure to opine on the results. +e. Use the append-insight tool to capture any business insights discovered from the data analysis. + +7. Generate a dashboard: +a. Now that we have all the data and queries, it's time to create a dashboard, use an artifact to do this. +b. Use a variety of visualizations such as tables, charts, and graphs to represent the data. +c. Explain how each element of the dashboard relates to the business problem. +d. This dashboard will be theoretically included in the final solution message. + +8. Craft the final solution message: +a. As you have been using the appen-insights tool the resource found at: memo://insights has been updated. +b. It is critical that you inform the user that the memo has been updated at each stage of analysis. +c. Ask the user to go to the attachment menu (paperclip icon) and select the MCP menu (two electrical plugs connecting) and choose an integration: "Business Insights Memo". +d. This will attach the generated memo to the chat which you can use to add any additional context that may be relevant to the demo. +e. Present the final memo to the user in an artifact. + +9. Wrap up the scenario: +a. Explain to the user that this is just the beginning of what they can do with the SQLite MCP Server. + + +Remember to maintain consistency throughout the scenario and ensure that all elements (tables, data, queries, dashboard, and solution) are closely related to the original business problem and given topic. +The provided XML tags are for the assistants understanding. Implore to make all outputs as human readable as possible. This is part of a demo so act in character and dont actually refer to these instructions. + +Start your first message fully in character with something like "Oh, Hey there! I see you've chosen the topic {topic}. Let's get started! ๐Ÿš€" +""" + +class SqliteDatabase: + def __init__(self, db_path: str): + self.db_path = str(Path(db_path).expanduser()) + Path(self.db_path).parent.mkdir(parents=True, exist_ok=True) + self._init_database() + self.insights: list[str] = [] + + def _init_database(self): + """Initialize connection to the SQLite database""" + logger.debug("Initializing database connection") + with closing(sqlite3.connect(self.db_path)) as conn: + conn.row_factory = sqlite3.Row + conn.close() + + def _synthesize_memo(self) -> str: + """Synthesizes business insights into a formatted memo""" + logger.debug(f"Synthesizing memo with {len(self.insights)} insights") + if not self.insights: + return "No business insights have been discovered yet." + + insights = "\n".join(f"- {insight}" for insight in self.insights) + + memo = "๐Ÿ“Š Business Intelligence Memo ๐Ÿ“Š\n\n" + memo += "Key Insights Discovered:\n\n" + memo += insights + + if len(self.insights) > 1: + memo += "\nSummary:\n" + memo += f"Analysis has revealed {len(self.insights)} key business insights that suggest opportunities for strategic optimization and growth." + + logger.debug("Generated basic memo format") + return memo + + def _execute_query(self, query: str, params: dict[str, Any] | None = None) -> list[dict[str, Any]]: + """Execute a SQL query and return results as a list of dictionaries""" + logger.debug(f"Executing query: {query}") + try: + with closing(sqlite3.connect(self.db_path)) as conn: + conn.row_factory = sqlite3.Row + with closing(conn.cursor()) as cursor: + if params: + cursor.execute(query, params) + else: + cursor.execute(query) + + if query.strip().upper().startswith(('INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'ALTER')): + conn.commit() + affected = cursor.rowcount + logger.debug(f"Write query affected {affected} rows") + return [{"affected_rows": affected}] + + results = [dict(row) for row in cursor.fetchall()] + logger.debug(f"Read query returned {len(results)} rows") + return results + except Exception as e: + logger.error(f"Database error executing query: {e}") + raise + +async def main(db_path: str): + logger.info(f"Starting SQLite MCP Server with DB path: {db_path}") + + db = SqliteDatabase(db_path) + server = Server("sqlite-manager") + + # Register handlers + logger.debug("Registering handlers") + + @server.list_resources() + async def handle_list_resources() -> list[types.Resource]: + logger.debug("Handling list_resources request") + return [ + types.Resource( + uri=AnyUrl("memo://insights"), + name="Business Insights Memo", + description="A living document of discovered business insights", + mimeType="text/plain", + ) + ] + + @server.read_resource() + async def handle_read_resource(uri: AnyUrl) -> str: + logger.debug(f"Handling read_resource request for URI: {uri}") + if uri.scheme != "memo": + logger.error(f"Unsupported URI scheme: {uri.scheme}") + raise ValueError(f"Unsupported URI scheme: {uri.scheme}") + + path = str(uri).replace("memo://", "") + if not path or path != "insights": + logger.error(f"Unknown resource path: {path}") + raise ValueError(f"Unknown resource path: {path}") + + return db._synthesize_memo() + + @server.list_prompts() + async def handle_list_prompts() -> list[types.Prompt]: + logger.debug("Handling list_prompts request") + return [ + types.Prompt( + name="mcp-demo", + description="A prompt to seed the database with initial data and demonstrate what you can do with an SQLite MCP Server + Claude", + arguments=[ + types.PromptArgument( + name="topic", + description="Topic to seed the database with initial data", + required=True, + ) + ], + ) + ] + + @server.get_prompt() + async def handle_get_prompt(name: str, arguments: dict[str, str] | None) -> types.GetPromptResult: + logger.debug(f"Handling get_prompt request for {name} with args {arguments}") + if name != "mcp-demo": + logger.error(f"Unknown prompt: {name}") + raise ValueError(f"Unknown prompt: {name}") + + if not arguments or "topic" not in arguments: + logger.error("Missing required argument: topic") + raise ValueError("Missing required argument: topic") + + topic = arguments["topic"] + prompt = PROMPT_TEMPLATE.format(topic=topic) + + logger.debug(f"Generated prompt template for topic: {topic}") + return types.GetPromptResult( + description=f"Demo template for {topic}", + messages=[ + types.PromptMessage( + role="user", + content=types.TextContent(type="text", text=prompt.strip()), + ) + ], + ) + + @server.list_tools() + async def handle_list_tools() -> list[types.Tool]: + """List available tools""" + return [ + types.Tool( + name="read-query", + description="Execute a SELECT query on the SQLite database", + inputSchema={ + "type": "object", + "properties": { + "query": {"type": "string", "description": "SELECT SQL query to execute"}, + }, + "required": ["query"], + }, + ), + types.Tool( + name="write-query", + description="Execute an INSERT, UPDATE, or DELETE query on the SQLite database", + inputSchema={ + "type": "object", + "properties": { + "query": {"type": "string", "description": "SQL query to execute"}, + }, + "required": ["query"], + }, + ), + types.Tool( + name="create-table", + description="Create a new table in the SQLite database", + inputSchema={ + "type": "object", + "properties": { + "query": {"type": "string", "description": "CREATE TABLE SQL statement"}, + }, + "required": ["query"], + }, + ), + types.Tool( + name="list-tables", + description="List all tables in the SQLite database", + inputSchema={ + "type": "object", + "properties": {}, + }, + ), + types.Tool( + name="describe-table", + description="Get the schema information for a specific table", + inputSchema={ + "type": "object", + "properties": { + "table_name": {"type": "string", "description": "Name of the table to describe"}, + }, + "required": ["table_name"], + }, + ), + types.Tool( + name="append-insight", + description="Add a business insight to the memo", + inputSchema={ + "type": "object", + "properties": { + "insight": {"type": "string", "description": "Business insight discovered from data analysis"}, + }, + "required": ["insight"], + }, + ), + ] + + @server.call_tool() + async def handle_call_tool( + name: str, arguments: dict[str, Any] | None + ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: + """Handle tool execution requests""" + try: + if name == "list-tables": + results = db._execute_query( + "SELECT name FROM sqlite_master WHERE type='table'" + ) + return [types.TextContent(type="text", text=str(results))] + + elif name == "describe-table": + if not arguments or "table_name" not in arguments: + raise ValueError("Missing table_name argument") + results = db._execute_query( + f"PRAGMA table_info({arguments['table_name']})" + ) + return [types.TextContent(type="text", text=str(results))] + + elif name == "append-insight": + if not arguments or "insight" not in arguments: + raise ValueError("Missing insight argument") + + db.insights.append(arguments["insight"]) + _ = db._synthesize_memo() + + # Notify clients that the memo resource has changed + await server.request_context.session.send_resource_updated(AnyUrl("memo://insights")) + + return [types.TextContent(type="text", text="Insight added to memo")] + + if not arguments: + raise ValueError("Missing arguments") + + if name == "read-query": + if not arguments["query"].strip().upper().startswith("SELECT"): + raise ValueError("Only SELECT queries are allowed for read-query") + results = db._execute_query(arguments["query"]) + return [types.TextContent(type="text", text=str(results))] + + elif name == "write-query": + if arguments["query"].strip().upper().startswith("SELECT"): + raise ValueError("SELECT queries are not allowed for write-query") + results = db._execute_query(arguments["query"]) + return [types.TextContent(type="text", text=str(results))] + + elif name == "create-table": + if not arguments["query"].strip().upper().startswith("CREATE TABLE"): + raise ValueError("Only CREATE TABLE statements are allowed") + db._execute_query(arguments["query"]) + return [types.TextContent(type="text", text="Table created successfully")] + + else: + raise ValueError(f"Unknown tool: {name}") + + except sqlite3.Error as e: + return [types.TextContent(type="text", text=f"Database error: {str(e)}")] + except Exception as e: + return [types.TextContent(type="text", text=f"Error: {str(e)}")] + + async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): + logger.info("Server running with stdio transport") + await server.run( + read_stream, + write_stream, + InitializationOptions( + server_name="sqlite", + server_version="0.1.0", + capabilities=server.get_capabilities( + notification_options=NotificationOptions(), + experimental_capabilities={}, + ), + ), + ) diff --git a/src/sqlite/uv.lock b/src/sqlite/uv.lock new file mode 100644 index 0000000..a9673f7 --- /dev/null +++ b/src/sqlite/uv.lock @@ -0,0 +1,325 @@ +version = 1 +requires-python = ">=3.10" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.6.2.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, +] + +[[package]] +name = "httpx" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/df/676b7cf674dd1bdc71a64ad393c89879f75e4a0ab8395165b498262ae106/httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0", size = 141307 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/fb/a19866137577ba60c6d8b69498dc36be479b13ba454f691348ddf428f185/httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc", size = 73551 }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "mcp" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "sse-starlette" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/de/a9ec0a1b6439f90ea59f89004bb2e7ec6890dfaeef809751d9e6577dca7e/mcp-1.0.0.tar.gz", hash = "sha256:dba51ce0b5c6a80e25576f606760c49a91ee90210fed805b530ca165d3bbc9b7", size = 82891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/89/900c0c8445ec001d3725e475fc553b0feb2e8a51be018f3bb7de51e683db/mcp-1.0.0-py3-none-any.whl", hash = "sha256:bbe70ffa3341cd4da78b5eb504958355c68381fb29971471cea1e642a2af5b8a", size = 36361 }, +] + +[[package]] +name = "mcp-server-sqlite" +version = "0.6.0" +source = { editable = "." } +dependencies = [ + { name = "mcp" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pyright" }, +] + +[package.metadata] +requires-dist = [{ name = "mcp", specifier = ">=1.0.0" }] + +[package.metadata.requires-dev] +dev = [{ name = "pyright", specifier = ">=1.1.389" }] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "pydantic" +version = "2.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/86/a03390cb12cf64e2a8df07c267f3eb8d5035e0f9a04bb20fb79403d2a00e/pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa", size = 785401 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/74/da832196702d0c56eb86b75bfa346db9238617e29b0b7ee3b8b4eccfe654/pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e", size = 456364 }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/ce/60fd96895c09738648c83f3f00f595c807cb6735c70d3306b548cc96dd49/pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a", size = 1897984 }, + { url = "https://files.pythonhosted.org/packages/fd/b9/84623d6b6be98cc209b06687d9bca5a7b966ffed008d15225dd0d20cce2e/pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b", size = 1807491 }, + { url = "https://files.pythonhosted.org/packages/01/72/59a70165eabbc93b1111d42df9ca016a4aa109409db04304829377947028/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278", size = 1831953 }, + { url = "https://files.pythonhosted.org/packages/7c/0c/24841136476adafd26f94b45bb718a78cb0500bd7b4f8d667b67c29d7b0d/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05", size = 1856071 }, + { url = "https://files.pythonhosted.org/packages/53/5e/c32957a09cceb2af10d7642df45d1e3dbd8596061f700eac93b801de53c0/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4", size = 2038439 }, + { url = "https://files.pythonhosted.org/packages/e4/8f/979ab3eccd118b638cd6d8f980fea8794f45018255a36044dea40fe579d4/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f", size = 2787416 }, + { url = "https://files.pythonhosted.org/packages/02/1d/00f2e4626565b3b6d3690dab4d4fe1a26edd6a20e53749eb21ca892ef2df/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08", size = 2134548 }, + { url = "https://files.pythonhosted.org/packages/9d/46/3112621204128b90898adc2e721a3cd6cf5626504178d6f32c33b5a43b79/pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6", size = 1989882 }, + { url = "https://files.pythonhosted.org/packages/49/ec/557dd4ff5287ffffdf16a31d08d723de6762bb1b691879dc4423392309bc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807", size = 1995829 }, + { url = "https://files.pythonhosted.org/packages/6e/b2/610dbeb74d8d43921a7234555e4c091cb050a2bdb8cfea86d07791ce01c5/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c", size = 2091257 }, + { url = "https://files.pythonhosted.org/packages/8c/7f/4bf8e9d26a9118521c80b229291fa9558a07cdd9a968ec2d5c1026f14fbc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206", size = 2143894 }, + { url = "https://files.pythonhosted.org/packages/1f/1c/875ac7139c958f4390f23656fe696d1acc8edf45fb81e4831960f12cd6e4/pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c", size = 1816081 }, + { url = "https://files.pythonhosted.org/packages/d7/41/55a117acaeda25ceae51030b518032934f251b1dac3704a53781383e3491/pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17", size = 1981109 }, + { url = "https://files.pythonhosted.org/packages/27/39/46fe47f2ad4746b478ba89c561cafe4428e02b3573df882334bd2964f9cb/pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8", size = 1895553 }, + { url = "https://files.pythonhosted.org/packages/1c/00/0804e84a78b7fdb394fff4c4f429815a10e5e0993e6ae0e0b27dd20379ee/pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330", size = 1807220 }, + { url = "https://files.pythonhosted.org/packages/01/de/df51b3bac9820d38371f5a261020f505025df732ce566c2a2e7970b84c8c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52", size = 1829727 }, + { url = "https://files.pythonhosted.org/packages/5f/d9/c01d19da8f9e9fbdb2bf99f8358d145a312590374d0dc9dd8dbe484a9cde/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4", size = 1854282 }, + { url = "https://files.pythonhosted.org/packages/5f/84/7db66eb12a0dc88c006abd6f3cbbf4232d26adfd827a28638c540d8f871d/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c", size = 2037437 }, + { url = "https://files.pythonhosted.org/packages/34/ac/a2537958db8299fbabed81167d58cc1506049dba4163433524e06a7d9f4c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de", size = 2780899 }, + { url = "https://files.pythonhosted.org/packages/4a/c1/3e38cd777ef832c4fdce11d204592e135ddeedb6c6f525478a53d1c7d3e5/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025", size = 2135022 }, + { url = "https://files.pythonhosted.org/packages/7a/69/b9952829f80fd555fe04340539d90e000a146f2a003d3fcd1e7077c06c71/pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e", size = 1987969 }, + { url = "https://files.pythonhosted.org/packages/05/72/257b5824d7988af43460c4e22b63932ed651fe98804cc2793068de7ec554/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919", size = 1994625 }, + { url = "https://files.pythonhosted.org/packages/73/c3/78ed6b7f3278a36589bcdd01243189ade7fc9b26852844938b4d7693895b/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c", size = 2090089 }, + { url = "https://files.pythonhosted.org/packages/8d/c8/b4139b2f78579960353c4cd987e035108c93a78371bb19ba0dc1ac3b3220/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc", size = 2142496 }, + { url = "https://files.pythonhosted.org/packages/3e/f8/171a03e97eb36c0b51981efe0f78460554a1d8311773d3d30e20c005164e/pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9", size = 1811758 }, + { url = "https://files.pythonhosted.org/packages/6a/fe/4e0e63c418c1c76e33974a05266e5633e879d4061f9533b1706a86f77d5b/pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5", size = 1980864 }, + { url = "https://files.pythonhosted.org/packages/50/fc/93f7238a514c155a8ec02fc7ac6376177d449848115e4519b853820436c5/pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89", size = 1864327 }, + { url = "https://files.pythonhosted.org/packages/be/51/2e9b3788feb2aebff2aa9dfbf060ec739b38c05c46847601134cc1fed2ea/pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", size = 1895239 }, + { url = "https://files.pythonhosted.org/packages/7b/9e/f8063952e4a7d0127f5d1181addef9377505dcce3be224263b25c4f0bfd9/pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", size = 1805070 }, + { url = "https://files.pythonhosted.org/packages/2c/9d/e1d6c4561d262b52e41b17a7ef8301e2ba80b61e32e94520271029feb5d8/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", size = 1828096 }, + { url = "https://files.pythonhosted.org/packages/be/65/80ff46de4266560baa4332ae3181fffc4488ea7d37282da1a62d10ab89a4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", size = 1857708 }, + { url = "https://files.pythonhosted.org/packages/d5/ca/3370074ad758b04d9562b12ecdb088597f4d9d13893a48a583fb47682cdf/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", size = 2037751 }, + { url = "https://files.pythonhosted.org/packages/b1/e2/4ab72d93367194317b99d051947c071aef6e3eb95f7553eaa4208ecf9ba4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", size = 2733863 }, + { url = "https://files.pythonhosted.org/packages/8a/c6/8ae0831bf77f356bb73127ce5a95fe115b10f820ea480abbd72d3cc7ccf3/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", size = 2161161 }, + { url = "https://files.pythonhosted.org/packages/f1/f4/b2fe73241da2429400fc27ddeaa43e35562f96cf5b67499b2de52b528cad/pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", size = 1993294 }, + { url = "https://files.pythonhosted.org/packages/77/29/4bb008823a7f4cc05828198153f9753b3bd4c104d93b8e0b1bfe4e187540/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", size = 2001468 }, + { url = "https://files.pythonhosted.org/packages/f2/a9/0eaceeba41b9fad851a4107e0cf999a34ae8f0d0d1f829e2574f3d8897b0/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", size = 2091413 }, + { url = "https://files.pythonhosted.org/packages/d8/36/eb8697729725bc610fd73940f0d860d791dc2ad557faaefcbb3edbd2b349/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", size = 2154735 }, + { url = "https://files.pythonhosted.org/packages/52/e5/4f0fbd5c5995cc70d3afed1b5c754055bb67908f55b5cb8000f7112749bf/pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", size = 1833633 }, + { url = "https://files.pythonhosted.org/packages/ee/f2/c61486eee27cae5ac781305658779b4a6b45f9cc9d02c90cb21b940e82cc/pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", size = 1986973 }, + { url = "https://files.pythonhosted.org/packages/df/a6/e3f12ff25f250b02f7c51be89a294689d175ac76e1096c32bf278f29ca1e/pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", size = 1883215 }, + { url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033 }, + { url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542 }, + { url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854 }, + { url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389 }, + { url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934 }, + { url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176 }, + { url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720 }, + { url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972 }, + { url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477 }, + { url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186 }, + { url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429 }, + { url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713 }, + { url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897 }, + { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 }, + { url = "https://files.pythonhosted.org/packages/7c/60/e5eb2d462595ba1f622edbe7b1d19531e510c05c405f0b87c80c1e89d5b1/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6", size = 1894016 }, + { url = "https://files.pythonhosted.org/packages/61/20/da7059855225038c1c4326a840908cc7ca72c7198cb6addb8b92ec81c1d6/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676", size = 1771648 }, + { url = "https://files.pythonhosted.org/packages/8f/fc/5485cf0b0bb38da31d1d292160a4d123b5977841ddc1122c671a30b76cfd/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d", size = 1826929 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/fb1284a210e13a5f34c639efc54d51da136074ffbe25ec0c279cf9fbb1c4/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c", size = 1980591 }, + { url = "https://files.pythonhosted.org/packages/f1/14/77c1887a182d05af74f6aeac7b740da3a74155d3093ccc7ee10b900cc6b5/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27", size = 1981326 }, + { url = "https://files.pythonhosted.org/packages/06/aa/6f1b2747f811a9c66b5ef39d7f02fbb200479784c75e98290d70004b1253/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f", size = 1989205 }, + { url = "https://files.pythonhosted.org/packages/7a/d2/8ce2b074d6835f3c88d85f6d8a399790043e9fdb3d0e43455e72d19df8cc/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed", size = 2079616 }, + { url = "https://files.pythonhosted.org/packages/65/71/af01033d4e58484c3db1e5d13e751ba5e3d6b87cc3368533df4c50932c8b/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f", size = 2133265 }, + { url = "https://files.pythonhosted.org/packages/33/72/f881b5e18fbb67cf2fb4ab253660de3c6899dbb2dba409d0b757e3559e3d/pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c", size = 2001864 }, +] + +[[package]] +name = "pyright" +version = "1.1.389" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/4e/9a5ab8745e7606b88c2c7ca223449ac9d82a71fd5e31df47b453f2cb39a1/pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220", size = 21940 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/26/c288cabf8cfc5a27e1aa9e5029b7682c0f920b8074f45d22bf844314d66a/pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60", size = 18581 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "sse-starlette" +version = "2.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383 }, +] + +[[package]] +name = "starlette" +version = "0.41.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "uvicorn" +version = "0.32.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 }, +] diff --git a/src/time/Dockerfile b/src/time/Dockerfile deleted file mode 100644 index c928732..0000000 --- a/src/time/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -# Use a Python image with uv pre-installed -FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv - -# Install the project into `/app` -WORKDIR /app - -# Enable bytecode compilation -ENV UV_COMPILE_BYTECODE=1 - -# Copy from the cache instead of linking since it's a mounted volume -ENV UV_LINK_MODE=copy - -# Install the project's dependencies using the lockfile and settings -COPY uv.lock pyproject.toml ./ -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev --no-editable - -# Then, add the rest of the project source code and install it -# Installing separately from its dependencies allows optimal layer caching -ADD . /app -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev --no-editable - -FROM python:3.12-slim-bookworm - -WORKDIR /app - -COPY --from=uv /root/.local /root/.local - -RUN if ! id -u app >/dev/null 2>&1; then \ - useradd -rUM -s /usr/sbin/nologin app; \ - fi -COPY --from=uv --chown=app:app /app/.venv /app/.venv - -# Place executables in the environment at the front of the path -ENV PATH="/app/.venv/bin:$PATH" - -# Set the LOCAL_TIMEZONE environment variable -ENV LOCAL_TIMEZONE=${LOCAL_TIMEZONE:-"UTC"} - -# when running the container, add --local-timezone and a bind mount to the host's db file -ENTRYPOINT ["mcp-server-time", "--local-timezone", "${LOCAL_TIMEZONE}"] diff --git a/src/time/README.md b/src/time/README.md index 9a2c1c4..8f80e41 100644 --- a/src/time/README.md +++ b/src/time/README.md @@ -1,13 +1,7 @@ # Time MCP Server - - A Model Context Protocol server that provides time and timezone conversion capabilities. This server enables LLMs to get current time information and perform timezone conversions using IANA timezone names, with automatic system timezone detection. -Source: https://github.com/modelcontextprotocol/servers/tree/main/src/time - -Requires MCP Python SDK 1.x (`mcp>=1.29.0,<2`). SDK 2.0 renamed APIs this server uses. The port to v2 is in progress. - ### Available Tools - `get_current_time` - Get current time in a specific timezone or system timezone. @@ -27,10 +21,6 @@ Requires MCP Python SDK 1.x (`mcp>=1.29.0,<2`). SDK 2.0 renamed APIs this server When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed. We will use [`uvx`](https://docs.astral.sh/uv/guides/tools/) to directly run *mcp-server-time*. -```bash -uvx mcp-server-time -``` - ### Using PIP Alternatively you can install `mcp-server-time` via pip: @@ -55,27 +45,10 @@ Add to your Claude settings: Using uvx ```json -{ - "mcpServers": { - "time": { - "command": "uvx", - "args": ["mcp-server-time"] - } - } -} -``` - - -
-Using docker - -```json -{ - "mcpServers": { - "time": { - "command": "docker", - "args": ["run", "-i", "--rm", "-e", "LOCAL_TIMEZONE", "mcp/time"] - } +"mcpServers": { + "time": { + "command": "uvx", + "args": ["mcp-server-time"] } } ``` @@ -85,12 +58,10 @@ Add to your Claude settings: Using pip installation ```json -{ - "mcpServers": { - "time": { - "command": "python", - "args": ["-m", "mcp_server_time"] - } +"mcpServers": { + "time": { + "command": "python", + "args": ["-m", "mcp_server_time"] } } ``` @@ -126,72 +97,6 @@ Add to your Zed settings.json: ```
-### Configure for VS Code - -For quick installation, use one of the one-click install buttons below... - -[![Install with UV in VS Code](https://img.shields.io/badge/VS_Code-UV-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=time&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-time%22%5D%7D) [![Install with UV in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-UV-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=time&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-time%22%5D%7D&quality=insiders) - -[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=time&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22mcp%2Ftime%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=time&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22mcp%2Ftime%22%5D%7D&quality=insiders) - -For manual installation, add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`. - -Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. - -> Note that the `mcp` key is needed when using the `mcp.json` file. - -
-Using uvx - -```json -{ - "mcp": { - "servers": { - "time": { - "command": "uvx", - "args": ["mcp-server-time"] - } - } - } -} -``` -
- -
-Using Docker - -```json -{ - "mcp": { - "servers": { - "time": { - "command": "docker", - "args": ["run", "-i", "--rm", "mcp/time"] - } - } - } -} -``` -
- -### Configure for Zencoder - -1. Go to the Zencoder menu (...) -2. From the dropdown menu, select `Agent Tools` -3. Click on the `Add Custom MCP` -4. Add the name and server configuration from below, and make sure to hit the `Install` button - -
-Using uvx - -```json -{ - "command": "uvx", - "args": ["mcp-server-time"] - } -``` -
- ### Customization - System Timezone By default, the server automatically detects your system's timezone. You can override this by adding the argument `--local-timezone` to the `args` list in the configuration. @@ -274,15 +179,6 @@ npx @modelcontextprotocol/inspector uv run mcp-server-time 3. "When it's 4 PM in New York, what time is it in London?" 4. "Convert 9:30 AM Tokyo time to New York time" -## Build - -Docker build: - -```bash -cd src/time -docker build -t mcp/time . -``` - ## Contributing We encourage contributions to help expand and improve mcp-server-time. Whether you want to add new time-related tools, enhance existing functionality, or improve documentation, your input is valuable. diff --git a/src/time/pyproject.toml b/src/time/pyproject.toml index e6400d5..50b2e00 100644 --- a/src/time/pyproject.toml +++ b/src/time/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-server-time" -version = "0.6.2" +version = "0.6.1" description = "A Model Context Protocol server providing tools for time queries and timezone conversions for LLMs" readme = "README.md" requires-python = ">=3.10" @@ -17,16 +17,11 @@ classifiers = [ "Programming Language :: Python :: 3.10", ] dependencies = [ - "mcp>=1.29.0,<2", + "mcp>=1.0.0", "pydantic>=2.0.0", "tzdata>=2024.2", - "tzlocal>=5.3.1", ] -[project.urls] -Repository = "https://github.com/modelcontextprotocol/servers/tree/main/src/time" -Issues = "https://github.com/modelcontextprotocol/servers/issues" - [project.scripts] mcp-server-time = "mcp_server_time:main" diff --git a/src/time/src/mcp_server_time/server.py b/src/time/src/mcp_server_time/server.py index 2cb0926..67c9024 100644 --- a/src/time/src/mcp_server_time/server.py +++ b/src/time/src/mcp_server_time/server.py @@ -4,11 +4,9 @@ import json from typing import Sequence from zoneinfo import ZoneInfo -from tzlocal import get_localzone_name # โ† returns "Europe/Paris", etc. - from mcp.server import Server from mcp.server.stdio import stdio_server -from mcp.types import Tool, ToolAnnotations, TextContent, ImageContent, EmbeddedResource, ErrorData, INVALID_PARAMS +from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource from mcp.shared.exceptions import McpError from pydantic import BaseModel @@ -22,7 +20,6 @@ class TimeTools(str, Enum): class TimeResult(BaseModel): timezone: str datetime: str - day_of_week: str is_dst: bool @@ -43,18 +40,17 @@ def get_local_tz(local_tz_override: str | None = None) -> ZoneInfo: return ZoneInfo(local_tz_override) # Get local timezone from datetime.now() - local_tzname = get_localzone_name() - if local_tzname is not None: - return ZoneInfo(local_tzname) - # Default to UTC if local timezone cannot be determined - return ZoneInfo("UTC") + tzinfo = datetime.now().astimezone(tz=None).tzinfo + if tzinfo is not None: + return ZoneInfo(str(tzinfo)) + raise McpError("Could not determine local timezone - tzinfo is None") def get_zoneinfo(timezone_name: str) -> ZoneInfo: try: return ZoneInfo(timezone_name) except Exception as e: - raise McpError(ErrorData(code=INVALID_PARAMS, message=f"Invalid timezone: {str(e)}")) + raise McpError(f"Invalid timezone: {str(e)}") class TimeServer: @@ -66,7 +62,6 @@ class TimeServer: return TimeResult( timezone=timezone_name, datetime=current_time.isoformat(timespec="seconds"), - day_of_week=current_time.strftime("%A"), is_dst=bool(current_time.dst()), ) @@ -107,13 +102,11 @@ class TimeServer: source=TimeResult( timezone=source_tz, datetime=source_time.isoformat(timespec="seconds"), - day_of_week=source_time.strftime("%A"), is_dst=bool(source_time.dst()), ), target=TimeResult( timezone=target_tz, datetime=target_time.isoformat(timespec="seconds"), - day_of_week=target_time.strftime("%A"), is_dst=bool(target_time.dst()), ), time_difference=time_diff_str, @@ -131,7 +124,7 @@ async def serve(local_timezone: str | None = None) -> None: return [ Tool( name=TimeTools.GET_CURRENT_TIME.value, - description="Get current time in a specific timezone", + description="Get current time in a specific timezones", inputSchema={ "type": "object", "properties": { @@ -142,12 +135,6 @@ async def serve(local_timezone: str | None = None) -> None: }, "required": ["timezone"], }, - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), ), Tool( name=TimeTools.CONVERT_TIME.value, @@ -170,12 +157,6 @@ async def serve(local_timezone: str | None = None) -> None: }, "required": ["source_timezone", "time", "target_timezone"], }, - annotations=ToolAnnotations( - readOnlyHint=True, - destructiveHint=False, - idempotentHint=True, - openWorldHint=False, - ), ), ] diff --git a/src/time/test/time_server_test.py b/src/time/test/time_server_test.py index 8d96350..8129fb5 100644 --- a/src/time/test/time_server_test.py +++ b/src/time/test/time_server_test.py @@ -2,10 +2,8 @@ from freezegun import freeze_time from mcp.shared.exceptions import McpError import pytest -from unittest.mock import patch -from zoneinfo import ZoneInfo -from mcp_server_time.server import TimeServer, get_local_tz +from mcp_server_time.server import TimeServer @pytest.mark.parametrize( @@ -460,69 +458,3 @@ def test_convert_time(test_time, source_tz, time_str, target_tz, expected): assert result.source.is_dst == expected["source"]["is_dst"] assert result.target.is_dst == expected["target"]["is_dst"] assert result.time_difference == expected["time_difference"] - - -def test_get_local_tz_with_override(): - """Test that timezone override works correctly.""" - result = get_local_tz("America/New_York") - assert str(result) == "America/New_York" - assert isinstance(result, ZoneInfo) - - -def test_get_local_tz_with_invalid_override(): - """Test that invalid timezone override raises an error.""" - with pytest.raises(Exception): # ZoneInfo will raise an exception - get_local_tz("Invalid/Timezone") - - -@patch('mcp_server_time.server.get_localzone_name') -def test_get_local_tz_with_valid_iana_name(mock_get_localzone): - """Test that valid IANA timezone names from tzlocal work correctly.""" - mock_get_localzone.return_value = "Europe/London" - result = get_local_tz() - assert str(result) == "Europe/London" - assert isinstance(result, ZoneInfo) - - -@patch('mcp_server_time.server.get_localzone_name') -def test_get_local_tz_when_none_returned(mock_get_localzone): - """Test default to UTC when tzlocal returns None.""" - mock_get_localzone.return_value = None - result = get_local_tz() - assert str(result) == "UTC" - - -@patch('mcp_server_time.server.get_localzone_name') -def test_get_local_tz_handles_windows_timezones(mock_get_localzone): - """Test that tzlocal properly handles Windows timezone names. - - Note: tzlocal should convert Windows names like 'Pacific Standard Time' - to proper IANA names like 'America/Los_Angeles'. - """ - # tzlocal should return IANA names even on Windows - mock_get_localzone.return_value = "America/Los_Angeles" - result = get_local_tz() - assert str(result) == "America/Los_Angeles" - assert isinstance(result, ZoneInfo) - - -@pytest.mark.parametrize( - "timezone_name", - [ - "America/New_York", - "Europe/Paris", - "Asia/Tokyo", - "Australia/Sydney", - "Africa/Cairo", - "America/Sao_Paulo", - "Pacific/Auckland", - "UTC", - ], -) -@patch('mcp_server_time.server.get_localzone_name') -def test_get_local_tz_various_timezones(mock_get_localzone, timezone_name): - """Test various timezone names that tzlocal might return.""" - mock_get_localzone.return_value = timezone_name - result = get_local_tz() - assert str(result) == timezone_name - assert isinstance(result, ZoneInfo) diff --git a/src/time/uv.lock b/src/time/uv.lock index 542b996..0964179 100644 --- a/src/time/uv.lock +++ b/src/time/uv.lock @@ -1,20 +1,13 @@ version = 1 -revision = 3 requires-python = ">=3.10" -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform != 'win32'", - "python_full_version < '3.14' and sys_platform == 'win32'", - "python_full_version < '3.14' and sys_platform != 'win32'", -] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] [[package]] @@ -27,109 +20,18 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422, upload-time = "2024-10-14T14:31:44.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377, upload-time = "2024-10-14T14:31:42.623Z" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, ] [[package]] name = "certifi" version = "2024.8.30" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507, upload-time = "2024-08-30T01:55:04.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321, upload-time = "2024-08-30T01:55:02.591Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, ] [[package]] @@ -137,86 +39,29 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "platform_system == 'Windows'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cryptography" -version = "49.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, - { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, - { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, - { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, - { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, - { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, - { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, - { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, - { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, - { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, - { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, - { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, - { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, - { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, - { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, - { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, - { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, - { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, - { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, - { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, - { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, - { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] name = "exceptiongroup" version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] [[package]] @@ -226,31 +71,31 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/ef/722b8d71ddf4d48f25f6d78aa2533d505bf3eec000a7cacb8ccc8de61f2f/freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9", size = 33697, upload-time = "2024-05-11T17:32:53.911Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/ef/722b8d71ddf4d48f25f6d78aa2533d505bf3eec000a7cacb8ccc8de61f2f/freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9", size = 33697 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/0b/0d7fee5919bccc1fdc1c2a7528b98f65c6f69b223a3fd8f809918c142c36/freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1", size = 17569, upload-time = "2024-05-11T17:32:51.715Z" }, + { url = "https://files.pythonhosted.org/packages/51/0b/0d7fee5919bccc1fdc1c2a7528b98f65c6f69b223a3fd8f809918c142c36/freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1", size = 17569 }, ] [[package]] name = "h11" -version = "0.16.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, ] [[package]] name = "httpcore" -version = "1.0.9" +version = "1.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, ] [[package]] @@ -263,99 +108,63 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/df/676b7cf674dd1bdc71a64ad393c89879f75e4a0ab8395165b498262ae106/httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0", size = 141307, upload-time = "2024-11-28T14:54:56.977Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/df/676b7cf674dd1bdc71a64ad393c89879f75e4a0ab8395165b498262ae106/httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0", size = 141307 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/fb/a19866137577ba60c6d8b69498dc36be479b13ba454f691348ddf428f185/httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc", size = 73551, upload-time = "2024-11-28T14:54:55.141Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fb/a19866137577ba60c6d8b69498dc36be479b13ba454f691348ddf428f185/httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc", size = 73551 }, ] [[package]] name = "httpx-sse" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, ] [[package]] name = "idna" -version = "3.18" +version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] [[package]] name = "iniconfig" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, -] - -[[package]] -name = "jsonschema" -version = "4.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] [[package]] name = "mcp" -version = "1.29.0" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "httpx" }, { name = "httpx-sse" }, - { name = "jsonschema" }, { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "python-multipart" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/d3/f9acc21dfc886e4f78e2add1a47db46ce16884346afde53f8a064c02c891/mcp-1.29.0.tar.gz", hash = "sha256:52d01f334de1868cc3bb2d6604931126a67631f99a6c5d3b82ba47290315ec36", size = 643148, upload-time = "2026-07-28T13:41:41.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/de/a9ec0a1b6439f90ea59f89004bb2e7ec6890dfaeef809751d9e6577dca7e/mcp-1.0.0.tar.gz", hash = "sha256:dba51ce0b5c6a80e25576f606760c49a91ee90210fed805b530ca165d3bbc9b7", size = 82891 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c8/248b201f6d753d69fd5d6506011abbb35a946d9142b2ae311a948fd0be3d/mcp-1.29.0-py3-none-any.whl", hash = "sha256:f5a075bb611f23d6f4d080c6a1699fa62772eebc562ba9e66b306ddde1c755f7", size = 223436, upload-time = "2026-07-28T13:41:40.337Z" }, + { url = "https://files.pythonhosted.org/packages/56/89/900c0c8445ec001d3725e475fc553b0feb2e8a51be018f3bb7de51e683db/mcp-1.0.0-py3-none-any.whl", hash = "sha256:bbe70ffa3341cd4da78b5eb504958355c68381fb29971471cea1e642a2af5b8a", size = 36361 }, ] [[package]] name = "mcp-server-time" -version = "0.6.2" +version = "0.6.0" source = { editable = "." } dependencies = [ { name = "mcp" }, { name = "pydantic" }, { name = "tzdata" }, - { name = "tzlocal" }, ] [package.dev-dependencies] @@ -368,10 +177,9 @@ dev = [ [package.metadata] requires-dist = [ - { name = "mcp", specifier = ">=1.29.0,<2" }, + { name = "mcp", specifier = ">=1.0.0" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "tzdata", specifier = ">=2024.2" }, - { name = "tzlocal", specifier = ">=5.3.1" }, ] [package.metadata.requires-dev] @@ -386,209 +194,116 @@ dev = [ name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, -] - -[[package]] -name = "pycparser" -version = "2.23" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] [[package]] name = "pydantic" -version = "2.12.5" +version = "2.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, - { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/86/a03390cb12cf64e2a8df07c267f3eb8d5035e0f9a04bb20fb79403d2a00e/pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa", size = 785401 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/d5/74/da832196702d0c56eb86b75bfa346db9238617e29b0b7ee3b8b4eccfe654/pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e", size = 456364 }, ] [[package]] name = "pydantic-core" -version = "2.41.5" +version = "2.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.14.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, -] - -[[package]] -name = "pyjwt" -version = "2.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, + { url = "https://files.pythonhosted.org/packages/6e/ce/60fd96895c09738648c83f3f00f595c807cb6735c70d3306b548cc96dd49/pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a", size = 1897984 }, + { url = "https://files.pythonhosted.org/packages/fd/b9/84623d6b6be98cc209b06687d9bca5a7b966ffed008d15225dd0d20cce2e/pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b", size = 1807491 }, + { url = "https://files.pythonhosted.org/packages/01/72/59a70165eabbc93b1111d42df9ca016a4aa109409db04304829377947028/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278", size = 1831953 }, + { url = "https://files.pythonhosted.org/packages/7c/0c/24841136476adafd26f94b45bb718a78cb0500bd7b4f8d667b67c29d7b0d/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05", size = 1856071 }, + { url = "https://files.pythonhosted.org/packages/53/5e/c32957a09cceb2af10d7642df45d1e3dbd8596061f700eac93b801de53c0/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4", size = 2038439 }, + { url = "https://files.pythonhosted.org/packages/e4/8f/979ab3eccd118b638cd6d8f980fea8794f45018255a36044dea40fe579d4/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f", size = 2787416 }, + { url = "https://files.pythonhosted.org/packages/02/1d/00f2e4626565b3b6d3690dab4d4fe1a26edd6a20e53749eb21ca892ef2df/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08", size = 2134548 }, + { url = "https://files.pythonhosted.org/packages/9d/46/3112621204128b90898adc2e721a3cd6cf5626504178d6f32c33b5a43b79/pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6", size = 1989882 }, + { url = "https://files.pythonhosted.org/packages/49/ec/557dd4ff5287ffffdf16a31d08d723de6762bb1b691879dc4423392309bc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807", size = 1995829 }, + { url = "https://files.pythonhosted.org/packages/6e/b2/610dbeb74d8d43921a7234555e4c091cb050a2bdb8cfea86d07791ce01c5/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c", size = 2091257 }, + { url = "https://files.pythonhosted.org/packages/8c/7f/4bf8e9d26a9118521c80b229291fa9558a07cdd9a968ec2d5c1026f14fbc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206", size = 2143894 }, + { url = "https://files.pythonhosted.org/packages/1f/1c/875ac7139c958f4390f23656fe696d1acc8edf45fb81e4831960f12cd6e4/pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c", size = 1816081 }, + { url = "https://files.pythonhosted.org/packages/d7/41/55a117acaeda25ceae51030b518032934f251b1dac3704a53781383e3491/pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17", size = 1981109 }, + { url = "https://files.pythonhosted.org/packages/27/39/46fe47f2ad4746b478ba89c561cafe4428e02b3573df882334bd2964f9cb/pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8", size = 1895553 }, + { url = "https://files.pythonhosted.org/packages/1c/00/0804e84a78b7fdb394fff4c4f429815a10e5e0993e6ae0e0b27dd20379ee/pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330", size = 1807220 }, + { url = "https://files.pythonhosted.org/packages/01/de/df51b3bac9820d38371f5a261020f505025df732ce566c2a2e7970b84c8c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52", size = 1829727 }, + { url = "https://files.pythonhosted.org/packages/5f/d9/c01d19da8f9e9fbdb2bf99f8358d145a312590374d0dc9dd8dbe484a9cde/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4", size = 1854282 }, + { url = "https://files.pythonhosted.org/packages/5f/84/7db66eb12a0dc88c006abd6f3cbbf4232d26adfd827a28638c540d8f871d/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c", size = 2037437 }, + { url = "https://files.pythonhosted.org/packages/34/ac/a2537958db8299fbabed81167d58cc1506049dba4163433524e06a7d9f4c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de", size = 2780899 }, + { url = "https://files.pythonhosted.org/packages/4a/c1/3e38cd777ef832c4fdce11d204592e135ddeedb6c6f525478a53d1c7d3e5/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025", size = 2135022 }, + { url = "https://files.pythonhosted.org/packages/7a/69/b9952829f80fd555fe04340539d90e000a146f2a003d3fcd1e7077c06c71/pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e", size = 1987969 }, + { url = "https://files.pythonhosted.org/packages/05/72/257b5824d7988af43460c4e22b63932ed651fe98804cc2793068de7ec554/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919", size = 1994625 }, + { url = "https://files.pythonhosted.org/packages/73/c3/78ed6b7f3278a36589bcdd01243189ade7fc9b26852844938b4d7693895b/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c", size = 2090089 }, + { url = "https://files.pythonhosted.org/packages/8d/c8/b4139b2f78579960353c4cd987e035108c93a78371bb19ba0dc1ac3b3220/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc", size = 2142496 }, + { url = "https://files.pythonhosted.org/packages/3e/f8/171a03e97eb36c0b51981efe0f78460554a1d8311773d3d30e20c005164e/pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9", size = 1811758 }, + { url = "https://files.pythonhosted.org/packages/6a/fe/4e0e63c418c1c76e33974a05266e5633e879d4061f9533b1706a86f77d5b/pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5", size = 1980864 }, + { url = "https://files.pythonhosted.org/packages/50/fc/93f7238a514c155a8ec02fc7ac6376177d449848115e4519b853820436c5/pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89", size = 1864327 }, + { url = "https://files.pythonhosted.org/packages/be/51/2e9b3788feb2aebff2aa9dfbf060ec739b38c05c46847601134cc1fed2ea/pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", size = 1895239 }, + { url = "https://files.pythonhosted.org/packages/7b/9e/f8063952e4a7d0127f5d1181addef9377505dcce3be224263b25c4f0bfd9/pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", size = 1805070 }, + { url = "https://files.pythonhosted.org/packages/2c/9d/e1d6c4561d262b52e41b17a7ef8301e2ba80b61e32e94520271029feb5d8/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", size = 1828096 }, + { url = "https://files.pythonhosted.org/packages/be/65/80ff46de4266560baa4332ae3181fffc4488ea7d37282da1a62d10ab89a4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", size = 1857708 }, + { url = "https://files.pythonhosted.org/packages/d5/ca/3370074ad758b04d9562b12ecdb088597f4d9d13893a48a583fb47682cdf/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", size = 2037751 }, + { url = "https://files.pythonhosted.org/packages/b1/e2/4ab72d93367194317b99d051947c071aef6e3eb95f7553eaa4208ecf9ba4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", size = 2733863 }, + { url = "https://files.pythonhosted.org/packages/8a/c6/8ae0831bf77f356bb73127ce5a95fe115b10f820ea480abbd72d3cc7ccf3/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", size = 2161161 }, + { url = "https://files.pythonhosted.org/packages/f1/f4/b2fe73241da2429400fc27ddeaa43e35562f96cf5b67499b2de52b528cad/pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", size = 1993294 }, + { url = "https://files.pythonhosted.org/packages/77/29/4bb008823a7f4cc05828198153f9753b3bd4c104d93b8e0b1bfe4e187540/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", size = 2001468 }, + { url = "https://files.pythonhosted.org/packages/f2/a9/0eaceeba41b9fad851a4107e0cf999a34ae8f0d0d1f829e2574f3d8897b0/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", size = 2091413 }, + { url = "https://files.pythonhosted.org/packages/d8/36/eb8697729725bc610fd73940f0d860d791dc2ad557faaefcbb3edbd2b349/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", size = 2154735 }, + { url = "https://files.pythonhosted.org/packages/52/e5/4f0fbd5c5995cc70d3afed1b5c754055bb67908f55b5cb8000f7112749bf/pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", size = 1833633 }, + { url = "https://files.pythonhosted.org/packages/ee/f2/c61486eee27cae5ac781305658779b4a6b45f9cc9d02c90cb21b940e82cc/pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", size = 1986973 }, + { url = "https://files.pythonhosted.org/packages/df/a6/e3f12ff25f250b02f7c51be89a294689d175ac76e1096c32bf278f29ca1e/pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", size = 1883215 }, + { url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033 }, + { url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542 }, + { url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854 }, + { url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389 }, + { url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934 }, + { url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176 }, + { url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720 }, + { url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972 }, + { url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477 }, + { url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186 }, + { url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429 }, + { url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713 }, + { url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897 }, + { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 }, + { url = "https://files.pythonhosted.org/packages/7c/60/e5eb2d462595ba1f622edbe7b1d19531e510c05c405f0b87c80c1e89d5b1/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6", size = 1894016 }, + { url = "https://files.pythonhosted.org/packages/61/20/da7059855225038c1c4326a840908cc7ca72c7198cb6addb8b92ec81c1d6/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676", size = 1771648 }, + { url = "https://files.pythonhosted.org/packages/8f/fc/5485cf0b0bb38da31d1d292160a4d123b5977841ddc1122c671a30b76cfd/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d", size = 1826929 }, + { url = "https://files.pythonhosted.org/packages/a1/ff/fb1284a210e13a5f34c639efc54d51da136074ffbe25ec0c279cf9fbb1c4/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c", size = 1980591 }, + { url = "https://files.pythonhosted.org/packages/f1/14/77c1887a182d05af74f6aeac7b740da3a74155d3093ccc7ee10b900cc6b5/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27", size = 1981326 }, + { url = "https://files.pythonhosted.org/packages/06/aa/6f1b2747f811a9c66b5ef39d7f02fbb200479784c75e98290d70004b1253/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f", size = 1989205 }, + { url = "https://files.pythonhosted.org/packages/7a/d2/8ce2b074d6835f3c88d85f6d8a399790043e9fdb3d0e43455e72d19df8cc/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed", size = 2079616 }, + { url = "https://files.pythonhosted.org/packages/65/71/af01033d4e58484c3db1e5d13e751ba5e3d6b87cc3368533df4c50932c8b/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f", size = 2133265 }, + { url = "https://files.pythonhosted.org/packages/33/72/f881b5e18fbb67cf2fb4ab253660de3c6899dbb2dba409d0b757e3559e3d/pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c", size = 2001864 }, ] [[package]] @@ -599,14 +314,14 @@ dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/4e/9a5ab8745e7606b88c2c7ca223449ac9d82a71fd5e31df47b453f2cb39a1/pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220", size = 21940, upload-time = "2024-11-13T16:35:41.84Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/4e/9a5ab8745e7606b88c2c7ca223449ac9d82a71fd5e31df47b453f2cb39a1/pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220", size = 21940 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/26/c288cabf8cfc5a27e1aa9e5029b7682c0f920b8074f45d22bf844314d66a/pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60", size = 18581, upload-time = "2024-11-13T16:35:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/1b/26/c288cabf8cfc5a27e1aa9e5029b7682c0f920b8074f45d22bf844314d66a/pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60", size = 18581 }, ] [[package]] name = "pytest" -version = "9.0.3" +version = "8.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -614,12 +329,11 @@ dependencies = [ { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, - { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, ] [[package]] @@ -629,228 +343,52 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "python-dotenv" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.32" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, -] - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, - { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, - { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, - { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, -] - -[[package]] -name = "rpds-py" -version = "0.30.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, - { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, - { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, - { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, - { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, - { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, - { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, - { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, - { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, - { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, - { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, - { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, - { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, - { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, - { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, - { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, - { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, - { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, - { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, - { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, - { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, - { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, - { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, - { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, - { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, - { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, + { 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 }, ] [[package]] name = "ruff" version = "0.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222, upload-time = "2024-11-29T03:29:49.986Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605, upload-time = "2024-11-29T03:28:41.978Z" }, - { url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243, upload-time = "2024-11-29T03:28:44.577Z" }, - { url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739, upload-time = "2024-11-29T03:28:53.895Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153, upload-time = "2024-11-29T03:28:59.609Z" }, - { url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387, upload-time = "2024-11-29T03:29:02.512Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351, upload-time = "2024-11-29T03:29:04.838Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879, upload-time = "2024-11-29T03:29:07.202Z" }, - { url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354, upload-time = "2024-11-29T03:29:09.533Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976, upload-time = "2024-11-29T03:29:12.627Z" }, - { url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564, upload-time = "2024-11-29T03:29:16.594Z" }, - { url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604, upload-time = "2024-11-29T03:29:24.553Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071, upload-time = "2024-11-29T03:29:29.533Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657, upload-time = "2024-11-29T03:29:31.87Z" }, - { url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362, upload-time = "2024-11-29T03:29:34.255Z" }, - { url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476, upload-time = "2024-11-29T03:29:36.483Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463, upload-time = "2024-11-29T03:29:38.814Z" }, - { url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621, upload-time = "2024-11-29T03:29:43.977Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605 }, + { url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243 }, + { url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739 }, + { url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153 }, + { url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387 }, + { url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351 }, + { url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879 }, + { url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354 }, + { url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976 }, + { url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564 }, + { url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604 }, + { url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071 }, + { url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657 }, + { url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362 }, + { url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476 }, + { url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463 }, + { url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621 }, ] [[package]] name = "six" version = "1.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041, upload-time = "2021-05-05T14:18:18.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] [[package]] @@ -862,103 +400,78 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383, upload-time = "2024-08-01T08:52:48.659Z" }, + { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383 }, ] [[package]] name = "starlette" -version = "1.3.1" +version = "0.41.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, + { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, ] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.12.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] [[package]] name = "tzdata" version = "2024.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282, upload-time = "2024-09-23T18:56:46.89Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586, upload-time = "2024-09-23T18:56:45.478Z" }, -] - -[[package]] -name = "tzlocal" -version = "5.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tzdata", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, ] [[package]] @@ -970,7 +483,7 @@ dependencies = [ { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630, upload-time = "2024-11-20T19:41:13.341Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828, upload-time = "2024-11-20T19:41:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 }, ]