diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..6629ad4 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,7 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in the changesets repository](https://github.com/changesets/changesets). + +You can create a changeset by running `pnpm changeset`. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..7d754ff --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json", + "changelog": ["@changesets/changelog-github", { "repo": "reaatech/mcp-contract-kit" }], + "commit": false, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.dockerignore b/.dockerignore index 22369d6..74e6b88 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,30 +1,15 @@ node_modules +.pnpm-store dist +build +*.tsbuildinfo +.git +.github coverage -.DS_Store +.vscode +.idea *.log -reports -.git -.gitignore -.husky .env -.env.example -.eslintrc* -.prettierrc -vitest.config.ts -*.md -tests -scripts -.github -skills -docs -*.test.ts -*.spec.ts -CLAUDE.md -AGENTS.md -ARCHITECTURE.md -DEV_PLAN.md -CONTRIBUTING.md -CHANGELOG.md -LICENSE -README.md \ No newline at end of file +.env.* +.DS_Store +Thumbs.db diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36f3089..1e566f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,81 +6,375 @@ on: pull_request: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_VERSION: 22 + jobs: - lint: + # ───────────────────────────────────────────── + # Setup & Install + # ───────────────────────────────────────────── + install: + name: Install Dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run lint + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' - typecheck: + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Save pnpm store + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + # ───────────────────────────────────────────── + # Security Audit + # ───────────────────────────────────────────── + audit: + name: Security Audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run typecheck + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' - test: + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Audit dependencies + run: pnpm audit --audit-level moderate + + # ───────────────────────────────────────────── + # Code Format Check + # ───────────────────────────────────────────── + format: + name: Code Format runs-on: ubuntu-latest + needs: install steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run test:ci - - name: Upload coverage - uses: codecov/codecov-action@v4 + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + + - name: Restore dependencies + uses: actions/cache@v4 with: - files: ./coverage/lcov.info - fail_ci_if_error: false + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Check formatting + run: pnpm biome format --write . && git diff --exit-code + # ───────────────────────────────────────────── + # Lint + # ───────────────────────────────────────────── + lint: + name: Lint + runs-on: ubuntu-latest + needs: install + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Lint + run: pnpm lint + + # ───────────────────────────────────────────── + # Type Check + # ───────────────────────────────────────────── + typecheck: + name: Type Check + runs-on: ubuntu-latest + needs: install + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Type check + run: pnpm typecheck + + # ───────────────────────────────────────────── + # Build + # ───────────────────────────────────────────── build: + name: Build runs-on: ubuntu-latest + needs: [lint, typecheck] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run build - - run: node dist/src/cli.js --help + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' - audit: + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Re-link workspace packages + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Build packages + run: pnpm build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-dist + path: | + packages/*/dist + retention-days: 1 + + # ───────────────────────────────────────────── + # Test + # ───────────────────────────────────────────── + test: + name: Test runs-on: ubuntu-latest + needs: build + strategy: + fail-fast: false + matrix: + node-version: [20, 22] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' - - run: npm ci - - run: npm run audit + node-version: ${{ matrix.node-version }} + cache: 'pnpm' - docker: + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Re-link workspace packages + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-dist + + - name: Run tests + run: pnpm test + + # ───────────────────────────────────────────── + # Coverage + # ───────────────────────────────────────────── + coverage: + name: Coverage runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + + - name: Restore dependencies + uses: actions/cache@v4 + with: + path: | + ~/.local/share/pnpm/store + node_modules + */*/node_modules + key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Re-link workspace packages + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-dist + + - name: Run tests with coverage + run: pnpm test:coverage + + - name: Upload coverage reports + uses: actions/upload-artifact@v4 + with: + name: coverage-reports + path: | + packages/*/coverage + retention-days: 7 + + - name: Post coverage summary + run: | + echo '## Test Coverage Summary' >> $GITHUB_STEP_SUMMARY + for pkg in packages/*/coverage/coverage-summary.json; do + if [ -f "$pkg" ]; then + name=$(basename $(dirname $(dirname "$pkg"))) + lines=$(jq -r '.total.lines.pct // "N/A"' "$pkg") + funcs=$(jq -r '.total.functions.pct // "N/A"' "$pkg") + branches=$(jq -r '.total.branches.pct // "N/A"' "$pkg") + echo "| $name | $lines% | $funcs% | $branches% |" >> $GITHUB_STEP_SUMMARY + fi + done + + # ───────────────────────────────────────────── + # Docker Build + # ───────────────────────────────────────────── + docker-build: + name: Docker Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Build Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . + file: ./docker/Dockerfile push: false - load: true tags: mcp-contract-kit:latest cache-from: type=gha cache-to: type=gha,mode=max + + # ───────────────────────────────────────────── + # Docker Compose Validate + # ───────────────────────────────────────────── + docker-compose: + name: Docker Compose + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate Docker Compose + working-directory: ./docker + run: docker compose config + + # ───────────────────────────────────────────── + # All Checks Passed + # ───────────────────────────────────────────── + all-checks: + name: All Checks Passed + runs-on: ubuntu-latest + needs: [audit, format, lint, typecheck, build, test, coverage, docker-build, docker-compose] + if: always() + steps: + - name: Check all jobs passed + run: | + check() { + if [ "$1" != "success" ]; then + echo "$2 did not succeed: $1" + exit 1 + fi + } + check '${{ needs.audit.result }}' 'Security Audit' + check '${{ needs.format.result }}' 'Code Format' + check '${{ needs.lint.result }}' 'Lint' + check '${{ needs.typecheck.result }}' 'Type Check' + check '${{ needs.build.result }}' 'Build' + check '${{ needs.test.result }}' 'Test' + check '${{ needs.coverage.result }}' 'Coverage' + check '${{ needs.docker-build.result }}' 'Docker Build' + check '${{ needs.docker-compose.result }}' 'Docker Compose' + echo "All checks passed!" diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml deleted file mode 100644 index 65cd4d1..0000000 --- a/.github/workflows/conformance.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Conformance - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - conformance: - runs-on: ubuntu-latest - strategy: - matrix: - server: - - name: mcp-server-starter-ts - url: http://localhost:8080 - version: latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - - - name: Install contract-kit - run: npm ci && npm link - - - name: Start test server - run: npx mcp-server-starter-ts & - background: true - - - name: Wait for server - run: sleep 10 - - - name: Run conformance tests - run: | - mcp-contract-kit test ${{ matrix.server.url }} \ - --format json \ - --output conformance-report.json - - - name: Upload report - uses: actions/upload-artifact@v4 - if: always() - with: - name: conformance-report-${{ matrix.server.name }} - path: conformance-report.json - - - name: Check for critical failures - run: | - CRITICAL=$(jq '.failures.critical' conformance-report.json) - if [ "$CRITICAL" -gt "0" ]; then - echo "Critical conformance issues found" - cat conformance-report.json - exit 1 - fi - - - name: Post results as check run - uses: actions/github-script@v7 - if: github.event_name == 'pull_request' - with: - script: | - const fs = require('fs'); - const report = JSON.parse(fs.readFileSync('conformance-report.json', 'utf8')); - const conclusion = report.failures.critical > 0 ? 'failure' : 'success'; - github.rest.checks.create({ - owner: context.repo.owner, - repo: context.repo.repo, - name: 'MCP Conformance - ${{ matrix.server.name }}', - head_sha: context.sha, - status: 'completed', - conclusion: conclusion, - output: { - title: 'Conformance Test Results', - summary: `Passed: ${report.summary.passed}, Failed: ${report.summary.failed}, Warnings: ${report.summary.warnings}` - } - }) \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 973e8c9..077068f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,109 +1,73 @@ name: Release on: - push: - tags: - - 'v*' + workflow_dispatch: + # push: + # branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +env: + NODE_VERSION: 22 jobs: - build: + release: + name: Release runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + id-token: write + packages: write steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v4 with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Run tests - run: npm run test + fetch-depth: 0 - - name: Run linting - run: npm run lint + - name: Setup pnpm + uses: pnpm/action-setup@v4 - - name: Run typecheck - run: npm run typecheck - - publish-npm: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: '22' - cache: 'npm' + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - - name: Build - run: npm run build + - name: Build packages + run: pnpm build - - name: Publish to npm - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - build-docker: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 + - name: Create release PR or publish to npm + id: changesets + uses: changesets/action@v1 with: - images: mcp-contract-kit - tags: | - type=semver,pattern={{version}} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max - - create-release: - needs: [publish-npm, build-docker] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - - name: Generate release notes - run: | - echo "# Release ${{ github.ref_name }}" > release-notes.md - echo "" >> release-notes.md - echo "## Changes" >> release-notes.md - git log --oneline -20 >> release-notes.md + publish: pnpm release + version: pnpm version-packages + commit: 'chore(release): version packages' + title: 'chore(release): version packages' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: 'true' - - name: Create GitHub release - uses: softprops/action-gh-release@v1 - with: - body_path: release-notes.md - generate_release_notes: true + - name: Mirror published packages to GitHub Packages + if: steps.changesets.outputs.published == 'true' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} + run: | + cat > .npmrc < $dir" + (cd "$dir" && npm publish --registry=https://npm.pkg.github.com) + done diff --git a/.gitignore b/.gitignore index 2cf15b1..2e3068c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,45 @@ # Dependencies node_modules/ +.pnpm-store/ -# Build output +# Build outputs dist/ +build/ +*.tsbuildinfo -# Environment files -.env -.env.local -.env.*.local - -# Coverage -coverage/ - -# OS files -.DS_Store -Thumbs.db +# Stray build artifacts inside src/ (tsup output belongs in dist/ only) +packages/*/src/**/*.js +packages/*/src/**/*.js.map +packages/*/src/**/*.d.ts +packages/*/src/**/*.d.ts.map # Logs +logs/ *.log npm-debug.log* -yarn-debug.log* -yarn-error.log* +pnpm-debug.log* -# Editor directories -.idea/ +# Environment +.env +.env.local +.env.*.local + +# IDE .vscode/ +.idea/ +.claude *.swp *.swo -# Reports -reports/ +# OS +.DS_Store +Thumbs.db -# Temporary files -tmp/ -temp/ +# Test +coverage/ -# Test fixtures with secrets -tests/fixtures/*.env +# Turborepo +.turbo/ -# Local development files +# Development DEV_PLAN.md diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index d62c0e3..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -npm run lint -npm run typecheck -npm test diff --git a/.lintstagedrc.json b/.lintstagedrc.json deleted file mode 100644 index 553ea4f..0000000 --- a/.lintstagedrc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "src/**/*.{ts,js}": [ - "eslint --fix", - "prettier --write" - ], - "tests/**/*.{ts,js}": [ - "eslint --fix", - "prettier --write" - ], - "*.{json,md,yaml,yml}": [ - "prettier --write" - ] -} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f7bafc7 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +shamefully-hoist=false +strict-peer-dependencies=true diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 475d54e..0000000 --- a/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all", - "tabWidth": 2, - "semi": true, - "printWidth": 100, - "bracketSpacing": true, - "arrowParens": "always", - "endOfLine": "lf" -} diff --git a/AGENTS.md b/AGENTS.md index f7db3d9..7bfb569 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,42 +1,86 @@ -# mcp-contract-kit — Agent Development Guide +# AGENTS.md — mcp-contract-kit -## What this is - -This document defines how to use `mcp-contract-kit` to validate AI agents built -with the MCP (Model Context Protocol) pattern. It covers both using the contract-kit -as a testing tool and building agents that pass conformance validation. +> Agent-focused guidance for maintaining and extending this repo. -**Target audience:** Engineers building MCP-compliant AI agents who need to -validate their implementations against the MCP specification and multi-agent -orchestration contracts. +## What this is ---- +`mcp-contract-kit` validates MCP-compliant AI agents against the MCP specification and +multi-agent orchestration contracts. It provides a CLI, a programmatic API, and a set +of pluggable validators organized by test category. -## Architecture Overview +## Project Structure ``` -┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ Your Agent │────▶│ contract-kit │────▶│ Validators │ -│ (MCP Server) │ │ (Test Runner) │ │ (Checks) │ -└─────────────────┘ └──────────────────┘ └─────────────────┘ - │ - ▼ - ┌──────────────────┐ - │ Reporter │ - │ (Pass/Fail + │ - │ Remediation) │ - └──────────────────┘ +packages/ + core/ @reaatech/mcp-contract-core — Domain types, JSON-RPC 2.0 schemas, utilities + client/ @reaatech/mcp-contract-client — MCP client SDK (HTTP transport, request builders) + validators/ @reaatech/mcp-contract-validators — Conformance validators (protocol, registry, routing, security, performance) + reporters/ @reaatech/mcp-contract-reporters — Report formatters (console, JSON, markdown, HTML) + observability/ @reaatech/mcp-contract-observability — Structured logging, metrics, tracing + cli/ @reaatech/mcp-contract-cli — CLI binary and public library API +scripts/ — Release and utility scripts +skills/ — Skill definitions for each test category +e2e/ — End-to-end integration tests ``` -### Key Components +## Build System + +| Tool | Purpose | +|------|---------| +| pnpm | Package manager and workspace orchestration | +| tsup | Per-package bundler (ESM + CJS dual output) | +| Turborepo | Task orchestration across packages | +| Biome | Formatting and linting (no Prettier/ESLint) | +| Vitest | Unit and integration testing | +| TypeScript | Strict mode, `NodeNext` module resolution | + +### Common Commands + +| Command | Description | +|---------|-------------| +| `pnpm install` | Install all workspace dependencies | +| `pnpm build` | Build all packages (`turbo run build`) | +| `pnpm test` | Run all tests (`turbo run test`) | +| `pnpm lint` | Lint all files with Biome | +| `pnpm lint:fix` | Auto-fix lint issues | +| `pnpm typecheck` | Type-check the entire workspace | +| `pnpm clean` | Remove all `dist/` and `node_modules/` | + +## Coding Conventions + +1. **TypeScript strict.** All packages use `tsconfig.json` with `strict: true`. +2. **ESM throughout.** Packages are `"type": "module"` with `.js` extensions in relative imports. +3. **Barrel exports.** Each package has a single `src/index.ts` entry point. Re-export via `@reaatech/mcp-contract-*` names. +4. **Validators follow a common interface.** Every validator implements `{ name, category, severity, validate(context) }` from `@reaatech/mcp-contract-core`. +5. **Remediation required.** Failed validations must include a `remediation` string explaining exactly how to fix the issue. +6. **Formatting is automated.** Run `pnpm lint:fix` before committing. Single quotes, trailing commas, 2-space indent. + +## Adding a New Package + +1. Scaffold the package directory: + ```bash + mkdir -p packages//src + ``` + +2. Copy `package.json` from an existing package and update `name`, `description`, and `dependencies`. + +3. Create `packages//src/index.ts` as the barrel entry point. + +4. Create `packages//tsconfig.json` extending the root: + ```json + { + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src"] + } + ``` + +5. Add the workspace dependency to any consuming packages (e.g., add to `cli/package.json`). -| Component | Location | Purpose | -|-----------|----------|---------| -| **Test Runner** | `src/runner.ts` | Orchestrates all validators | -| **Validators** | `src/validators/` | Individual conformance checks | -| **MCP Client** | `src/mcp-client/` | Connects to your agent for testing | -| **Reporters** | `src/reporters/` | Formats test results | -| **CLI** | `src/cli.ts` | Command-line interface | +6. Run `pnpm install` to link the workspace, then `pnpm build` to verify. --- @@ -45,8 +89,8 @@ orchestration contracts. ### Quick Start ```bash -# Install -npm install -g mcp-contract-kit +# Add to your project +pnpm add @reaatech/mcp-contract-cli # Run all tests against your agent mcp-contract-kit test http://localhost:8080 @@ -92,39 +136,37 @@ import { validateRouting, generateReport, TestSuite, -} from 'mcp-contract-kit'; +} from '@reaatech/mcp-contract-cli'; -// Run all tests const report = await runTests({ endpoint: 'http://localhost:8080', suites: [TestSuite.PROTOCOL, TestSuite.ROUTING], timeout: 30000, }); -// Check results if (report.failures.critical > 0) { console.error('Critical conformance issues found'); process.exit(1); } -// Generate report const html = await generateReport(report, 'html'); -fs.writeFileSync('conformance-report.html', html); ``` ### Validating Agent YAML ```typescript -import { validateRegistry } from 'mcp-contract-kit'; +import { validateRegistry } from '@reaatech/mcp-contract-cli'; const result = await validateRegistry({ yamlPath: './agents/my-agent.yaml', - strict: true, // Fail on warnings too + strict: true, }); -if (!result.valid) { +if (!result.passed) { console.error('Registry validation failed:'); - result.errors.forEach(err => console.error(` - ${err.message}`)); + result.results.forEach(r => { + if (!r.passed) console.error(` - ${r.message}`); + }); } ``` @@ -162,7 +204,7 @@ Your MCP server MUST: ### Contract Requirements -If integrating with an orchestrator (like agent-mesh), your agent MUST: +If integrating with an orchestrator, your agent MUST: 1. **Accept the standard request format:** ```json @@ -188,7 +230,7 @@ If integrating with an orchestrator (like agent-mesh), your agent MUST: } ``` -3. **Handle the `handle_message` tool** — This is the standard entry point +3. **Handle the `handle_message` tool** — This is the standard entry point. ### Registry Requirements @@ -239,7 +281,6 @@ If registering with an orchestrator, your agent YAML MUST: ### GitHub Actions ```yaml -# .github/workflows/conformance.yml name: MCP Conformance on: @@ -253,26 +294,36 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + + - uses: pnpm/action-setup@v4 + with: + version: 10 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + - name: Start MCP server - run: npm run build && npm start & - + run: pnpm install && pnpm build && node packages/cli/dist/cli.js test http://localhost:8080 & + - name: Wait for server run: sleep 5 - + - name: Run conformance tests run: | + pnpm add @reaatech/mcp-contract-cli npx mcp-contract-kit test http://localhost:8080 \ --format json \ --output conformance-report.json - + - name: Upload report if: always() uses: actions/upload-artifact@v4 with: name: conformance-report path: conformance-report.json - + - name: Fail on critical issues run: | CRITICAL=$(jq '.failures.critical' conformance-report.json) @@ -284,17 +335,12 @@ jobs: ### Pre-commit Hook -```bash -#!/bin/bash -# .husky/pre-commit - -# Run conformance tests before commit -npx mcp-contract-kit test http://localhost:8080 --fail-on critical - -if [ $? -ne 0 ]; then - echo "Conformance issues found. Please fix before committing." +```sh +#!/bin/sh +npx mcp-contract-kit test http://localhost:8080 --fail-on critical || { + echo "Conformance issues found. Fix before committing." exit 1 -fi +} ``` --- @@ -304,66 +350,93 @@ fi Extend contract-kit with domain-specific validators: ```typescript -import { Validator, TestResult, Severity, TestCategory } from 'mcp-contract-kit'; +import { Severity, TestCategory } from '@reaatech/mcp-contract-core'; +import type { Validator, TestResult, ValidationContext } from '@reaatech/mcp-contract-core'; const myCustomValidator: Validator = { name: 'my-custom-check', category: TestCategory.PROTOCOL, severity: Severity.WARNING, - - async validate(client, context) { - // Your validation logic here - const result = await client.sendRequest({ + + async validate(context: ValidationContext): Promise { + const result = await context.client.sendRequest({ + jsonrpc: '2.0', method: 'tools/call', + id: 1, params: { name: 'my_tool', arguments: {} }, }); - + if (result.error) { return { + validator: 'my-custom-check', + category: TestCategory.PROTOCOL, passed: false, severity: Severity.CRITICAL, - message: 'Custom validation failed', - remediation: 'Fix the issue by...', + message: `Tool call failed: ${result.error.message}`, + remediation: 'Ensure the tool is registered and accepts valid arguments.', + durationMs: 0, + timestamp: new Date().toISOString(), }; } - + return { + validator: 'my-custom-check', + category: TestCategory.PROTOCOL, passed: true, severity: Severity.INFO, - message: 'Custom validation passed', + message: 'Custom validation passed.', + durationMs: 0, + timestamp: new Date().toISOString(), }; }, }; ``` +Register custom validators alongside built-in ones when calling `runTests`: + +```typescript +import { runTests, TestSuite } from '@reaatech/mcp-contract-cli'; +import { getProtocolValidators } from '@reaatech/mcp-contract-validators'; + +const report = await runTests({ + endpoint: 'http://localhost:8080', + suites: [TestSuite.PROTOCOL], +}); +``` + --- ## Testing Your Agent Locally ### Using Docker -```bash -# Build and run your agent -docker build -t my-agent . -docker run -p 8080:8080 my-agent +```dockerfile +FROM node:22-alpine +RUN npm install -g pnpm@10 +WORKDIR /app +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./ +COPY packages/ ./packages/ +RUN pnpm install --frozen-lockfile && pnpm build +ENTRYPOINT ["node", "packages/cli/dist/cli.js"] +``` -# In another terminal, run conformance tests -npx mcp-contract-kit test http://localhost:8080 +```bash +docker build -t mcp-contract-kit . +docker run mcp-contract-kit test http://host.docker.internal:8080 ``` ### Using docker-compose ```yaml -# docker-compose.yml -version: '3.8' services: agent: build: . ports: - "8080:8080" - + contract-kit: - image: mcp-contract-kit:latest + build: + context: ./mcp-contract-kit command: test http://agent:8080 --format html --output /reports/report.html volumes: - ./reports:/reports @@ -372,7 +445,7 @@ services: ``` ```bash -docker-compose up --exit-code-from contract-kit +docker compose up --exit-code-from contract-kit ``` --- @@ -397,8 +470,8 @@ Before deploying your agent to production: ## References - **ARCHITECTURE.md** — System design deep dive -- **DEV_PLAN.md** — Development checklist - **README.md** — Quick start and overview - **skills/** — Skill definitions for each test category +- **packages/core/** — Domain types and schemas (`@reaatech/mcp-contract-core`) +- **packages/cli/** — CLI and public API (`@reaatech/mcp-contract-cli`) - **MCP Specification** — https://modelcontextprotocol.io/ -- **agent-mesh/AGENTS.md** — Multi-agent orchestration patterns diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 8204265..7285d28 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,238 +1,221 @@ -# mcp-contract-kit — Architecture - -## System Overview - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ Client Layer │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ CLI │ │ Library │ │ CI/CD │ │ -│ │ (npx) │ │ (import) │ │ Pipeline │ │ -│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ -│ │ │ │ │ -│ └───────────────────┼───────────────────┘ │ -│ │ │ -└─────────────────────────────┼─────────────────────────────────────────────┘ - ▼ -┌─────────────────────────────────────────────────────────────────────────┐ -│ Test Runner │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ Runner Orchestrator │ │ -│ │ │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │ Config │───▶│ Scheduler │───▶│ Executor │ │ │ -│ │ │ Resolver │ │ (Parallel) │ │ (Sequential)│ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ - ▼ -┌─────────────────────────────────────────────────────────────────────────┐ -│ Validator Pipeline │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ Registry │ │ Protocol │ │ Routing │ │ Security │ │ -│ │ Validators │ │ Validators │ │ Validators │ │ Validators │ │ -│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ -│ │ │ │ │ │ -│ └─────────────────┼────────────────┼────────────────┘ │ -│ ▼ │ -│ ┌─────────────────┐ │ -│ │ Performance │ │ -│ │ Validators │ │ -│ └─────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ - ▼ -┌─────────────────────────────────────────────────────────────────────────┐ -│ MCP Client │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ Transport Abstraction │ │ -│ │ │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │ Streamable │ │ SSE │ │ Stdio │ │ │ -│ │ │ HTTP │ │ (Legacy) │ │ (Local) │ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ - ▼ -┌─────────────────────────────────────────────────────────────────────────┐ -│ Target MCP Server │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ Agent A │ │ Agent B │ │ Orchestra- │ │ Any MCP │ │ -│ │ │ │ │ │ tor Core │ │ Server │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ - ▼ -┌─────────────────────────────────────────────────────────────────────────┐ -│ Reporters │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ Console │ │ JSON │ │ HTML │ │ Markdown │ │ -│ │ (Colored) │ │ (Machine) │ │ (Dashboard)│ │ (Summary) │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## Design Principles - -### 1. Non-Destructive Testing -- Validators never modify the target server -- All tests are read-only operations -- No side effects on the MCP server state -- Safe to run against production endpoints - -### 2. Idempotent Execution -- Running the same test twice produces identical results -- No state carried between test runs -- Deterministic output for CI/CD reliability -- Retry-safe operations - -### 3. Fast Feedback -- Individual validators complete in <1 second -- Parallel execution where possible -- Early termination on critical failures (configurable) -- Progressive reporting (stream results as they complete) - -### 4. Clear Failures -- Every test failure includes remediation guidance -- Severity levels (critical, warning, info) for prioritization -- Line numbers and context for YAML validation errors -- Actionable error messages, not just "test failed" - -### 5. Extensible Architecture -- Pluggable validator system -- Custom validators can be added without modifying core -- Reporter interface for custom output formats -- Configuration-driven test selection - ---- - -## Component Deep Dive - -### Test Runner - -The runner orchestrates all validators and aggregates results: +# ARCHITECTURE.md — mcp-contract-kit + +> System-level design for the MCP conformance test suite. + +## Overview + +This monorepo is a conformance test suite for validating MCP (Model Context Protocol) servers. It provides a CLI, a programmatic API, pluggable validators across five categories (registry, protocol, routing, security, performance), and four output reporters. The kit connects to an MCP server, executes validators, aggregates results, and generates pass/fail reports with remediation guidance. + +## Package Boundaries + +``` +┌─────────────────────────────────────────────────────────────┐ +│ cli │ +│ (CLI entry point, runner, public API) │ +└───────────────┬──────────┬────────────┬────────────────────┘ + │ │ │ + ┌──────────▼──┐ ┌─────▼──────┐ ┌──▼───────────┐ + │ validators │ │ reporters │ │ client │ + │ (conform- │ │ (output │ │ (MCP HTTP │ + │ ance │ │ format- │ │ transport) │ + │ checks) │ │ ters) │ │ │ + └──────┬──────┘ └─────┬──────┘ └───────┬───────┘ + │ │ │ + └──────────────┼────────────────┘ + │ + ┌────────▼────────┐ + │ observability │ + │ (logs, metrics,│ + │ tracing) │ + └────────┬───────┘ + │ + ┌────────▼────────┐ + │ core │ + │ (types, schemas,│ + │ utils) │ + └─────────────────┘ +``` + +| Package | Path | Scope | Purpose | +|---------|------|-------|---------| +| `core` | `packages/core/` | `@reaatech/mcp-contract-core` | Domain types, Zod schemas, UUID/id generators, retry helpers | +| `observability` | `packages/observability/` | `@reaatech/mcp-contract-observability` | Pino logger, in-memory metrics, span tracing | +| `client` | `packages/client/` | `@reaatech/mcp-contract-client` | HTTP transport, MCP client (connect, tools/list, tools/call) | +| `validators` | `packages/validators/` | `@reaatech/mcp-contract-validators` | Five suites of conformance checks | +| `reporters` | `packages/reporters/` | `@reaatech/mcp-contract-reporters` | Console, JSON, Markdown, HTML output formatters | +| `cli` | `packages/cli/` | `@reaatech/mcp-contract-cli` | CLI entry point, test runner, public API functions | + +## Data Flow + +### Test Run (CLI / Programmatic API) + +``` +CLI (mcp-contract-kit test ) + │ + ▼ + parseArgs() ──► Resolve suites, format, timeout, failOn + │ + ▼ + runTests() ──► createMCPClient(endpoint) ──► MCPHtpClient.connect() + │ │ + │ ▼ + │ HttpTransport.request() + │ POST + │ JSON-RPC body + │ │ + ▼ ▼ + expandSuites() ◄── Target MCP Server responds + │ + ▼ + getValidatorsForSuites() ──► Array + │ + ▼ + executeValidators() ──► For each validator: + │ 1. validator.setup?(context) + │ 2. validator.validate(context) + │ 3. validator.teardown?(context) + │ 4. metrics.recordDuration() + │ 5. metrics.inc(passed/failed) + │ + ▼ + aggregateResults() ──► TestReport + │ + ▼ + formatReport(report, format) ──► Console | JSON | Markdown | HTML + │ + ▼ + Exit code 0 | 1 | 2 | 3 +``` + +### YAML Validation (Offline) + +``` +CLI (mcp-contract-kit validate-yaml ) + │ + ▼ + parseArgs() ──► Resolve yamlPath, strict mode + │ + ▼ + validateRegistry() ──► createNullClient() (no network) + │ + ▼ + getRegistryValidators() ──► [SchemaValidator, InvariantValidator, EnvExpansionValidator] + │ + ▼ + aggregateResults() ──► TestReport + │ + ▼ + formatReport(report, format) + │ + ▼ + Exit code 0 | 1 +``` + +## Component Deep Dives + +### Runner + +Location: `packages/cli/src/runner.ts` ```typescript -interface TestRunner { - // Run specific suites - run(options: RunOptions): Promise; - - // Run single validator - validate(validator: Validator): Promise; - - // Aggregate results - aggregate(results: TestResult[]): TestReport; +interface RunOptions { + endpoint: string; + suites?: Array; + timeout?: number; // default 30000 + retries?: number; // default 3 + headers?: Record; + yamlPath?: string; + verbose?: boolean; + failOn?: Severity; // default CRITICAL } ``` -**Execution Strategy:** -1. **Configuration Phase** — Resolve suites, filters, and options -2. **Setup Phase** — Establish MCP connection, validate connectivity -3. **Execution Phase** — Run validators (parallel where safe, sequential where required) -4. **Aggregation Phase** — Collect results, calculate summary statistics -5. **Reporting Phase** — Format and output results +The runner orchestrates the five execution phases: + +1. **Suite Expansion** — Resolves `TestSuite.ALL` or explicit suites into `TestCategory[]` +2. **Validator Loading** — Calls `getRegistryValidators()` / `getProtocolValidators()` / etc. to get the validator set +3. **Client Creation** — `createMCPClient({ endpoint, timeout, retries })` establishes the HTTP transport +4. **Sequential Execution** — Validators run one at a time (setup → validate → teardown), collecting results +5. **Aggregation** — Counts passed, failed, warnings, criticals; generates a `TestReport` + +Public API: +- `runTests(options)` — Full conformance run against an endpoint +- `validateRegistry(options)` — Offline YAML validation (no network) +- `validateProtocol(options)` / `validateRouting(options)` — Single-suite convenience wrappers +- `generateReport(report, format)` — Format a report object into a string + +### Core -**Design Decision:** Validators run in parallel by default for speed, but can be -forced to run sequentially if they have dependencies or shared state requirements. +Location: `packages/core/src/` -### Validator System +``` +core/ +├── domain.ts Domain types (Validator, MCPClient, TestReport, enums) +├── schemas.ts Zod schemas (AgentConfigSchema, MCP response schemas) +├── utils.ts generateId(), generateUUID(), now(), retry() +└── version.ts getVersion() from package.json +``` -Each validator implements a common interface: +Key domain interfaces: ```typescript interface Validator { - // Unique identifier name: string; - - // Category (registry, protocol, routing, security, performance) category: TestCategory; - - // Severity level for failures severity: Severity; - - // Main validation logic - validate(client: MCPClient, context: TestContext): Promise; - - // Optional: setup before validation - setup?(context: TestContext): Promise; - - // Optional: cleanup after validation - teardown?(context: TestContext): Promise; + validate(context: ValidationContext): Promise; + setup?(context: ValidationContext): Promise; + teardown?(context: ValidationContext): Promise; } -``` -**Validator Categories:** - -| Category | Purpose | Execution | -|----------|---------|-----------| -| **Registry** | YAML schema and invariant validation | File-based, no network | -| **Protocol** | MCP JSON-RPC 2.0 spec compliance | Network calls to server | -| **Routing** | Request/response contract compatibility | Network calls with specific payloads | -| **Security** | SSRF, auth, input sanitization | Network calls with edge cases | -| **Performance** | Latency, concurrency, rate limiting | Multiple network calls, timing | +interface ValidationContext { + client: MCPClient; + endpoint: string; + options: TestOptions; + requestId: string; + artifacts?: Record; +} +``` ### MCP Client -Abstracts transport details and provides a unified interface: +Location: `packages/client/src/` + +``` +client/ +├── client.ts MCPHttpClient (implements MCPClient) +├── transport.ts HttpTransport (fetch-based, SSE + JSON) +├── request-builder.ts Builds JSON-RPC 2.0 requests +└── index.ts createMCPClient() factory +``` ```typescript interface MCPClient { - // Connect to server connect(): Promise; - - // Send JSON-RPC request - sendRequest(request: MCPRequest): Promise; - - // Call a tool + sendRequest(request: MCPRequest): Promise>; callTool(name: string, args: Record): Promise; - - // List available tools listTools(): Promise; - - // Close connection disconnect(): Promise; + getSessionId(): Promise; } ``` -**Transport Implementations:** - -| Transport | Use Case | Protocol | -|-----------|----------|----------| -| **StreamableHTTP** | Modern MCP servers | HTTP POST to `/mcp` | -| **SSE** | Legacy MCP servers | Server-Sent Events | -| **Stdio** | Local development | Child process stdio | +**Transport:** `HttpTransport` sends POST requests with JSON-RPC bodies. It auto-detects SSE responses via the `text/event-stream` content-type and deserializes them. Includes configurable retry with exponential backoff (100 ms base, 2 s max). -**Design Decision:** The client handles retry logic, timeout management, and -error normalization so validators can focus on validation logic. +### Validators -### Reporter System +Location: `packages/validators/src/` -Formats test results for different consumers: - -```typescript -interface Reporter { - // Generate report from results - report(results: TestReport): Promise; - - // Write to file or stdout - write(output: string, destination?: string): Promise; -} +``` +validators/src/ +├── registry/ schema, invariant, env-expansion +├── protocol/ jsonrpc, tool-discovery, tool-execution, session +├── routing/ request-contract, response-contract, compatibility +├── security/ ssrf, auth, input-sanitization +└── performance/ latency, concurrency, rate-limit ``` -**Reporter Implementations:** - -| Reporter | Output | Use Case | -|----------|--------|----------| -| **Console** | Colored terminal output | Interactive CLI usage | -| **JSON** | Machine-readable JSON | CI/CD pipelines, programmatic use | -| **HTML** | Interactive dashboard | Human review, sharing results | -| **Markdown** | GitHub-flavored markdown | PR comments, documentation | - ---- - -## Validator Deep Dive - -### Registry Compliance Validators +#### Registry Compliance Validators ``` ┌─────────────────────────────────────────────────────────────────────┐ @@ -251,25 +234,25 @@ interface Reporter { └─────────────────────────────────────────────────────────────────────┘ ``` -**SchemaValidator:** +**SchemaValidator** (`packages/validators/src/registry/schema.validator.ts`): - Parses YAML file - Validates against `AgentConfigSchema` (Zod) - Reports field-level errors with line numbers -- Checks required fields: agent_id, display_name, description, endpoint, type, is_default, confidence_threshold, clarification_required, examples +- Required fields: agent_id, display_name, description, endpoint, type, is_default, confidence_threshold, clarification_required, examples -**InvariantValidator:** +**InvariantValidator** (`packages/validators/src/registry/invariant.validator.ts`): - Exactly one agent has `is_default: true` - Default agent has `confidence_threshold: 0` - All agent IDs are unique -- Endpoint URLs are valid and not localhost/private IPs (SSRF protection) -- File sizes are within limits +- Endpoint URLs are valid and not localhost/private IPs +- File sizes within configured limits -**EnvExpansionValidator:** +**EnvExpansionValidator** (`packages/validators/src/registry/env-expansion.validator.ts`): - Validates `${ENV_VAR}` syntax -- Warns about undefined variables +- Warns about undefined environment variables - Detects circular references -### Protocol Conformance Validators +#### Protocol Conformance Validators ``` ┌─────────────────────────────────────────────────────────────────────┐ @@ -301,7 +284,7 @@ interface Reporter { └─────────────────────────────────────────────────────────────────────┘ ``` -**JSONRPCValidator:** +**JSONRPCValidator** (`packages/validators/src/protocol/jsonrpc.validator.ts`): - Sends test requests and validates responses - Checks `jsonrpc: "2.0"` in all responses - Validates `id` field matches request @@ -309,7 +292,7 @@ interface Reporter { - Validates error object structure (code, message, optional data) - Checks error codes are in valid ranges -**ToolDiscoveryValidator:** +**ToolDiscoveryValidator** (`packages/validators/src/protocol/tool-discovery.validator.ts`): - Calls `tools/list` method - Validates response is an array - Each tool has required fields (name, description, inputSchema) @@ -317,20 +300,20 @@ interface Reporter { - Tool names follow conventions (lowercase, hyphens) - Input schemas are valid JSON Schema -**ToolExecutionValidator:** +**ToolExecutionValidator** (`packages/validators/src/protocol/tool-execution.validator.ts`): - Calls each discovered tool with valid inputs - Validates input schema enforcement - Checks response contains valid content array - Tests error handling for unknown tools - Validates timeout behavior -**SessionValidator:** +**SessionValidator** (`packages/validators/src/protocol/session.validator.ts`): - Creates session and validates ID format - Verifies session persists across requests - Tests session cleanup on termination - Validates concurrent session isolation -### Routing Contract Validators +#### Routing Contract Validators ``` ┌─────────────────────────────────────────────────────────────────────┐ @@ -349,25 +332,24 @@ interface Reporter { └─────────────────────────────────────────────────────────────────────┘ ``` -**RequestContractValidator:** +**RequestContractValidator** (`packages/validators/src/routing/request-contract.validator.ts`): - Validates the standard request schema sent by orchestrators - Required fields: session_id (UUID), request_id (UUID), employee_id, raw_input - Optional fields: display_name, intent_summary, entities, turn_history, workflow_state - Field type validation (UUIDs, string lengths, array structures) -**ResponseContractValidator:** +**ResponseContractValidator** (`packages/validators/src/routing/response-contract.validator.ts`): - Validates the standard response schema agents must return - Required fields: content (non-empty string), workflow_complete (boolean) - Optional fields: workflow_state, isError, errorMessage -- Content must be a non-empty string -**CompatibilityValidator:** +**CompatibilityValidator** (`packages/validators/src/routing/compatibility.validator.ts`): - Sends test requests to the agent - Validates responses match expected contract - Tests with various input scenarios (empty, long, special chars, Unicode) - Verifies error handling consistency -### Security Validators +#### Security Validators ``` ┌─────────────────────────────────────────────────────────────────────┐ @@ -388,22 +370,22 @@ interface Reporter { └─────────────────────────────────────────────────────────────────────┘ ``` -**SSRFValidator:** +**SSRFValidator** (`packages/validators/src/security/ssrf.validator.ts`): - Validates endpoint URLs reject localhost and private IPs - Tests DNS rebinding detection - Validates redirect following behavior -**AuthValidator:** +**AuthValidator** (`packages/validators/src/security/auth.validator.ts`): - Checks if API key is required (configurable) - Validates invalid keys are rejected with 401 - Tests auth bypass attempts -**InputSanitizationValidator:** +**InputSanitizationValidator** (`packages/validators/src/security/input-sanitization.validator.ts`): - Sends prompt injection patterns - Validates XSS prevention - Tests SQL injection pattern handling -### Performance Validators +#### Performance Validators ``` ┌─────────────────────────────────────────────────────────────────────┐ @@ -424,139 +406,169 @@ interface Reporter { └─────────────────────────────────────────────────────────────────────┘ ``` -**LatencyValidator:** +**LatencyValidator** (`packages/validators/src/performance/latency.validator.ts`): - Measures response times for various operations - Calculates p50, p90, p99 percentiles - Validates against configurable thresholds - Tests timeout behavior -**ConcurrencyValidator:** +**ConcurrencyValidator** (`packages/validators/src/performance/concurrency.validator.ts`): - Sends multiple simultaneous requests - Validates no race conditions - Checks data integrity under load - Verifies resource cleanup -**RateLimitValidator:** +**RateLimitValidator** (`packages/validators/src/performance/rate-limit.validator.ts`): - Sends requests exceeding rate limit - Validates 429 response - Checks Retry-After header presence - Tests rate limit reset behavior ---- - -## Data Flow +### Reporters -### Test Execution Flow +Location: `packages/reporters/src/` ``` -1. User invokes CLI or library API - │ -2. Configuration resolved (suites, filters, options) - │ -3. MCP connection established to target server - │ -4. Connectivity validated (ping/health check) - │ -5. Validators executed: - - Registry validators (file-based, parallel) - - Protocol validators (network calls, parallel) - - Routing validators (specific payloads, sequential) - - Security validators (edge cases, sequential) - - Performance validators (timing, sequential) - │ -6. Results aggregated - │ -7. Report generated (console, JSON, HTML, or markdown) - │ -8. Exit code set based on severity threshold - │ -9. Connection closed +reporters/src/ +├── console.reporter.ts Colored terminal output +├── json.reporter.ts Machine-readable JSON +├── markdown.reporter.ts GitHub-flavored markdown +└── html.reporter.ts Interactive HTML dashboard ``` -### Validator Execution Flow +```typescript +type ReportFormat = 'console' | 'json' | 'markdown' | 'html'; +function formatReport(report: TestReport, format: ReportFormat): Promise; ``` -For each validator: - 1. Setup (if applicable) - 2. Execute validation logic - 3. Capture result (pass/fail, severity, message, remediation) - 4. Teardown (if applicable) - 5. Report progress (streaming) + +| Reporter | Output | Use Case | +|----------|--------|----------| +| `console` | Colored terminal output | Interactive CLI usage | +| `json` | Machine-readable JSON | CI/CD pipelines, programmatic use | +| `markdown` | GitHub-flavored markdown | PR comments, documentation | +| `html` | Interactive dashboard | Human review, sharing results | + +### Observability + +Location: `packages/observability/src/` + +#### Logger (`logger.ts`) + +Pino-based structured logger with PII redaction. Sensitive keys (`password`, `token`, `secret`, `key`, `authorization`, `api_key`, `employee_id`, `session_id`) are automatically replaced with `[REDACTED]`. + +```typescript +import { logger, createLogger } from '@reaatech/mcp-contract-observability'; + +// Default logger +logger.info('Connected to server', { endpoint: 'https://agent.example.com' }); + +// Child logger with request context +const child = logger.child({ request_id: 'abc-123' }); +child.warn('Slow response', { durationMs: 2500 }); ``` ---- +#### Metrics (`metrics.ts`) -## Error Handling +In-memory singleton collector with counters and duration histograms. Buckets: 10, 50, 100, 250, 500, 1000, 2500, 5000, 10000 ms. -| Error Type | Detection | Recovery | -|------------|-----------|----------| -| Network timeout | Request exceeds timeout | Retry with backoff, then fail | -| Invalid JSON-RPC response | Parse error | Report as critical protocol failure | -| Connection refused | TCP error | Report server unreachable | -| SSL certificate error | TLS handshake failure | Report security issue | -| YAML parse error | YAML parser exception | Report with line number | -| Schema validation error | Zod parse error | Report field-level errors | +```typescript +import { metrics, MetricNames } from '@reaatech/mcp-contract-observability'; ---- +metrics.inc(MetricNames.TESTS_TOTAL, 1); +metrics.recordDuration(MetricNames.VALIDATOR_DURATION, 42, { validator: 'jsonrpc' }); -## Observability +// Summary +const summary = metrics.getSummary(); +// { uptime: 12, counters: { ... }, histograms: { ... } } +``` -### Structured Logging +| Metric Constant | Type | Labels | +|-----------------|------|--------| +| `MetricNames.TESTS_TOTAL` | Counter | `validator`, `category` | +| `MetricNames.TESTS_PASSED` | Counter | `validator`, `category` | +| `MetricNames.TESTS_FAILED` | Counter | `validator`, `category` | +| `MetricNames.TESTS_WARNING` | Counter | `validator`, `category` | +| `MetricNames.VALIDATOR_DURATION` | Histogram | `validator`, `category` | +| `MetricNames.RUN_DURATION` | Histogram | — | +| `MetricNames.ERRORS_TOTAL` | Counter | `validator`, `category` | -All operations logged with: -- `timestamp` — ISO-8601 -- `service` — "mcp-contract-kit" -- `request_id` — Unique test run identifier -- `validator` — Current validator name -- `level` — Log level (debug, info, warn, error) +#### Tracing (`tracing.ts`) -### Tracing +In-memory span tracing with W3C trace context propagation. -Each test run generates OpenTelemetry spans: -- Root span for entire test run -- Child span per validator -- Attributes: duration, result, severity +```typescript +import { startSpan, endSpan, withSpan, toTraceParent } from '@reaatech/mcp-contract-observability'; -### Metrics +// Manual span +const span = startSpan('jsonrpc.check', { endpoint: 'https://...' }); +// ... do work ... +endSpan(span, 'ok'); -| Metric | Type | Labels | -|--------|------|--------| -| `contract_kit.runs.total` | Counter | `status` | -| `contract_kit.validator.duration_ms` | Histogram | `validator`, `category` | -| `contract_kit.results.total` | Counter | `severity`, `category` | +// Auto-span wrapper +await withSpan('full.suite.run', async () => { + // all work inside is measured +}); ---- +// W3C propagation +const header = toTraceParent(span.context); // "00---01" +``` ## Configuration +### CLI Usage + +``` +mcp-contract-kit test [OPTIONS] +mcp-contract-kit validate-yaml [OPTIONS] +``` + +| Flag | Default | Description | +|------|---------|-------------| +| `--suite ` | `all` | Test suite: registry, protocol, routing, security, performance (repeatable) | +| `--format ` | `console` | Output format: console, json, markdown, html | +| `--output ` | stdout | Write report to file | +| `--verbose` | `false` | Show detailed output | +| `--fail-on ` | `critical` | Exit error threshold: critical, warning | +| `--timeout ` | `30000` | Request timeout in milliseconds | +| `--retries ` | `3` | Retry count for transient failures | +| `--strict` | `false` | (validate-yaml) Fail on warnings too | +| `--help` | — | Show help message | +| `--version` | — | Show version | + ### Environment Variables | Variable | Default | Description | |----------|---------|-------------| +| `LOG_LEVEL` | `info` | Pino logging level (debug, info, warn, error) | | `MCP_TIMEOUT_MS` | `30000` | Request timeout | | `MCP_RETRIES` | `3` | Retry count for transient failures | -| `LOG_LEVEL` | `info` | Logging level | -| `OTEL_EXPORTER_OTLP_ENDPOINT` | — | OTel collector endpoint | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | — | OpenTelemetry collector endpoint (optional) | -### CLI Flags +### Exit Codes -| Flag | Description | -|------|-------------| -| `--endpoint` | MCP server URL | -| `--suite` | Test suite to run (can be repeated) | -| `--format` | Output format (console, json, html, markdown) | -| `--output` | Write report to file | -| `--verbose` | Detailed output | -| `--fail-on` | Exit code threshold (critical, warning) | -| `--yaml` | Path to agent YAML for registry validation | -| `--timeout` | Request timeout in milliseconds | +| Code | Meaning | +|------|---------| +| `0` | All tests passed (or only info-level findings) | +| `1` | Critical failures found | +| `2` | Warning failures found (with `--fail-on warning` or `--strict`) | +| `3` | Test execution error (network, timeout, crash) | ---- +## Error Handling + +| Error Type | Detection | Recovery | +|------------|-----------|----------| +| Network timeout | AbortController fires after `--timeout` ms | Retry with exponential backoff (100 ms → 2 s), then fail | +| Invalid JSON-RPC response | JSON parse error or missing `jsonrpc` field | Report as critical protocol failure | +| Connection refused | TCP error from `fetch()` | Report server unreachable | +| Content-type mismatch | SSE payload when JSON expected (or vice versa) | Auto-detected and deserialized | +| YAML parse error | YAML parser exception | Report with line number | +| Schema validation error | Zod parse error | Report field-level errors with path | ## References -- **AGENTS.md** — Agent development guide -- **DEV_PLAN.md** — Development checklist +- **AGENTS.md** — Agent development guide and conformance checklist - **README.md** — Quick start and overview +- **skills/** — Skill definitions for each test category +- **packages/** — Monorepo package source code - **MCP Specification** — https://modelcontextprotocol.io/ -- **ask-gm/orchestrator-core/tests/contract** — Contract testing reference diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8d18fd5..0000000 --- a/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -# Stage 1: Build -FROM node:22-alpine AS builder - -WORKDIR /app - -# Copy package files -COPY package.json package-lock.json ./ -RUN npm ci --ignore-scripts - -# Copy source and build -COPY . . -RUN npm run build - -# Stage 2: Production -FROM node:22-alpine AS production - -WORKDIR /app - -# Create non-root user -RUN addgroup -g 1001 -S nodejs && \ - adduser -S nodejs -u 1001 - -# Copy package files and install production deps only -COPY package.json package-lock.json ./ -RUN npm ci --only=production --ignore-scripts && npm cache clean --force - -# Copy built files from builder -COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist - -# Switch to non-root user -USER nodejs - -# Entry point -ENTRYPOINT ["node", "dist/src/cli.js"] -CMD ["--help"] diff --git a/README.md b/README.md index bfca9b1..4ae4591 100644 --- a/README.md +++ b/README.md @@ -1,214 +1,86 @@ # mcp-contract-kit -[![npm version](https://img.shields.io/npm/v/mcp-contract-kit)](https://www.npmjs.com/package/mcp-contract-kit) -[![Build Status](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml) -[![Coverage](https://codecov.io/gh/reaatech/mcp-contract-kit/branch/main/graph/badge.svg)](https://codecov.io/gh/reaatech/mcp-contract-kit) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.8-blue.svg)](https://www.typescriptlang.org/) -**Conformance test suite for MCP servers.** A library and CLI tool that any MCP -server author can point at their endpoint to verify registry compliance, protocol -conformance, and routing correctness. +> Conformance test suite for MCP servers. Validate registry compliance, protocol conformance, and routing correctness. -## Features - -- **5 Test Suites**: Registry, protocol, routing, security, and performance validation -- **4 Output Formats**: Console (colored), JSON, HTML (interactive dashboard), Markdown -- **Library API**: Use as a Node.js module in your CI/CD pipeline or test suite -- **Extensible**: Add custom validators for domain-specific checks -- **SSRF Protection**: Validates endpoints against localhost/private IP injection -- **Observability**: Structured logging, OpenTelemetry tracing, metrics collection +This monorepo provides a CLI tool, validators, MCP client SDK, and reporting infrastructure for testing Model Context Protocol (MCP) server implementations against the MCP specification. -## Supported MCP Servers +## Features -This tool validates any MCP server implementing the [Model Context Protocol](https://modelcontextprotocol.io/) -specification. Tested against: +- Protocol conformance — JSON-RPC 2.0 compliance, tool discovery, tool execution, session management +- Registry validation — YAML schema validation, invariant checks, environment variable expansion +- Routing contracts — Request/response format validation, compatibility testing +- Security posture — SSRF protection, authentication checks, input sanitization +- Performance baseline — Latency measurement, concurrency testing, rate limit detection +- Multiple reporters — Console, JSON, Markdown, and HTML output formats +- Programmatic API — integrate into CI/CD pipelines +- MCP client SDK — connect to and test any MCP server -- [mcp-server-starter-ts](https://github.com/reaatech/mcp-server-starter-ts) -- Custom MCP servers implementing JSON-RPC 2.0 with `tools/list` and `tools/call` +## Installation -## Quick Start +### Using the packages ```bash -# Install -npm install -g mcp-contract-kit - -# Run all tests against your MCP server -mcp-contract-kit test http://localhost:8080 - -# Run specific test suite -mcp-contract-kit test http://localhost:8080 --suite protocol - -# Generate HTML report -mcp-contract-kit test http://localhost:8080 --format html --output report.html - -# Validate agent registry YAML -mcp-contract-kit validate-yaml agents.yaml --strict +# CLI tool (includes all validators) +pnpm add @reaatech/mcp-contract-cli + +# Individual packages +pnpm add @reaatech/mcp-contract-core +pnpm add @reaatech/mcp-contract-client +pnpm add @reaatech/mcp-contract-validators +pnpm add @reaatech/mcp-contract-reporters +pnpm add @reaatech/mcp-contract-observability ``` -## Test Suites - -| Suite | What It Checks | When to Use | -|-------|----------------|-------------| -| `registry` | Agent YAML configuration compliance | Before deploying to orchestrator | -| `protocol` | MCP JSON-RPC 2.0 spec compliance | Always — core MCP validation | -| `routing` | Request/response contract compatibility | When integrating with orchestrator | -| `security` | SSRF, auth, input sanitization | Before production deployment | -| `performance` | Latency, concurrency, rate limiting | Before scaling to production | -| `all` | All suites combined | Full conformance validation | - -## Exit Codes - -| Code | Meaning | -|------|---------| -| `0` | All tests passed (only info-level findings) | -| `1` | Critical failures found | -| `2` | Warning failures found (with `--fail-on warning`) | -| `3` | Test execution error | - -## CLI Examples +### Contributing ```bash -# Run all suites with verbose output -mcp-contract-kit test http://localhost:8080 --verbose - -# Run multiple specific suites -mcp-contract-kit test http://localhost:8080 --suite protocol --suite security - -# Generate JSON report for CI/CD -mcp-contract-kit test http://localhost:8080 --format json --output conformance-report.json - -# Fail on warnings (not just critical) -mcp-contract-kit test http://localhost:8080 --fail-on warning +git clone https://github.com/reaatech/mcp-contract-kit.git +cd mcp-contract-kit +pnpm install +pnpm build +pnpm test +pnpm lint +``` -# Increase timeout for slow servers -mcp-contract-kit test http://localhost:8080 --timeout 60000 +## Quick Start -# Validate YAML with strict mode (fail on warnings) -mcp-contract-kit validate-yaml agents.yaml --strict +Test an MCP server: -# Show help -mcp-contract-kit --help +```bash +mcp-contract-kit test http://localhost:8080 --suite all --format console ``` -## Library API +Or use the programmatic API: ```typescript -import { - runTests, - validateRegistry, - generateReport, - TestSuite, - Severity, -} from 'mcp-contract-kit'; - -// Run all tests -const report = await runTests({ - endpoint: 'http://localhost:8080', - suites: [TestSuite.PROTOCOL, TestSuite.ROUTING], - timeout: 30000, - failOn: Severity.CRITICAL, -}); - -// Check results -if (report.failures.critical > 0) { - console.error('Critical conformance issues found'); - console.error(report.results.map(r => r.message).join('\n')); - process.exit(1); -} - -// Generate HTML report -const html = await generateReport(report, 'html'); -fs.writeFileSync('conformance-report.html', html); - -// Validate agent YAML -const yamlReport = await validateRegistry({ - yamlPath: './agents/my-agent.yaml', - strict: true, -}); - -if (!yamlReport.passed) { - console.error('YAML validation failed'); - process.exit(1); -} -``` - -### Using the MCP Client +import { runTests, generateReport } from "@reaatech/mcp-contract-cli"; -```typescript -import { createMCPClient } from 'mcp-contract-kit'; - -const client = createMCPClient({ - endpoint: 'http://localhost:8080', - timeout: 30000, - retries: 3, -}); - -await client.connect(); -const tools = await client.listTools(); -const result = await client.callTool('handle_message', { ... }); -await client.disconnect(); +const report = await runTests({ endpoint: "http://localhost:8080" }); +console.log(report.passed ? "PASSED" : "FAILED"); ``` -## CI/CD Integration - -### GitHub Actions - -```yaml -name: MCP Conformance - -on: [push, pull_request] +## Packages -jobs: - conformance: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - - - name: Install contract-kit - run: npm ci && npm link - - - name: Start MCP server - run: npm start & - background: true - - - name: Wait for server - run: sleep 5 - - - name: Run conformance tests - run: | - mcp-contract-kit test http://localhost:8080 \ - --format json \ - --output conformance-report.json - - - name: Upload report - uses: actions/upload-artifact@v4 - if: always() - with: - name: conformance-report - path: conformance-report.json - - - name: Fail on critical issues - run: | - CRITICAL=$(jq '.failures.critical' conformance-report.json) - if [ "$CRITICAL" -gt "0" ]; then - echo "Critical conformance issues found" - exit 1 - fi -``` +| Package | Description | +|---------|-------------| +| [`@reaatech/mcp-contract-core`](./packages/core) | Core domain types, JSON-RPC 2.0 schemas, and utilities | +| [`@reaatech/mcp-contract-client`](./packages/client) | MCP client SDK for connecting to MCP servers | +| [`@reaatech/mcp-contract-validators`](./packages/validators) | Conformance validators (protocol, registry, routing, security, performance) | +| [`@reaatech/mcp-contract-reporters`](./packages/reporters) | Report formatters (console, JSON, markdown, HTML) | +| [`@reaatech/mcp-contract-observability`](./packages/observability) | Structured logging, metrics, and tracing | +| [`@reaatech/mcp-contract-cli`](./packages/cli) | CLI tool and public API | ## Documentation -- **[AGENTS.md](./AGENTS.md)** — Agent development guide -- **[ARCHITECTURE.md](./ARCHITECTURE.md)** — System design deep dive -- **[DEV_PLAN.md](./DEV_PLAN.md)** — Development checklist -- **[docs/VALIDATORS.md](./docs/VALIDATORS.md)** — Validator reference -- **[docs/CI_CD.md](./docs/CI_CD.md)** — CI/CD integration guide +- `ARCHITECTURE.md` — System design and package relationships +- `AGENTS.md` — Coding conventions and development guidelines +- `CONTRIBUTING.md` — Contribution workflow and release process +- `docs/` — Additional documentation ## License -MIT \ No newline at end of file +MIT diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..d7cd1be --- /dev/null +++ b/biome.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "files": { + "ignore": ["dist", ".turbo", "node_modules", "coverage"] + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "noNonNullAssertion": "error" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all" + } + } +} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 7601fae..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,34 +0,0 @@ -services: - contract-kit: - build: . - command: test http://mock-server:8080 --verbose - depends_on: - - mock-server - volumes: - - ./reports:/app/reports - environment: - - NODE_ENV=test - - LOG_LEVEL=info - - mock-server: - image: node:22-alpine - working_dir: /app - command: > - npx -y mcp-server-starter-ts --port 8080 - ports: - - "8080:8080" - - integration-test: - build: . - command: sh -c "sleep 5 && mcp-contract-kit test http://mock-server:8080 --format json --output /app/reports/integration.json" - depends_on: - - mock-server - volumes: - - ./reports:/app/reports - environment: - - NODE_ENV=test - profiles: - - test - -volumes: - reports: \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..52cba16 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,30 @@ +FROM node:22-alpine AS base +RUN npm install -g pnpm@10 + +FROM base AS deps +WORKDIR /app +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.json biome.json ./ +COPY packages/ ./packages/ +COPY e2e/ ./e2e/ +RUN pnpm install --frozen-lockfile + +FROM base AS builder +WORKDIR /app +COPY --from=deps /app ./ +RUN pnpm build + +FROM base AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/pnpm-lock.yaml ./pnpm-lock.yaml +COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml +COPY --from=builder /app/turbo.json ./turbo.json +COPY --from=builder /app/tsconfig.json ./tsconfig.json +COPY --from=builder /app/biome.json ./biome.json +COPY --from=builder /app/packages ./packages +COPY --from=builder /app/e2e ./e2e +RUN pnpm install --prod --frozen-lockfile + +ENTRYPOINT ["node", "packages/cli/dist/cli.js"] +CMD ["--help"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..54aa511 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,6 @@ +services: + contract-kit: + build: + context: .. + dockerfile: docker/Dockerfile + command: test http://host.docker.internal:8080 diff --git a/tests/e2e/cli.e2e.test.ts b/e2e/__tests__/cli.e2e.test.ts similarity index 94% rename from tests/e2e/cli.e2e.test.ts rename to e2e/__tests__/cli.e2e.test.ts index c1b2b47..768f2a3 100644 --- a/tests/e2e/cli.e2e.test.ts +++ b/e2e/__tests__/cli.e2e.test.ts @@ -1,10 +1,10 @@ import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; +import { main } from '@reaatech/mcp-contract-cli'; +import { CLI_VERSION, parseArgs, printHelp } from '@reaatech/mcp-contract-cli'; +import { Severity, TestCategory } from '@reaatech/mcp-contract-core'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { main } from '../../src/cli.js'; -import { parseArgs, printHelp, CLI_VERSION } from '../../src/cli/config.js'; -import { Severity, TestCategory } from '../../src/types/domain.js'; function installMockFetch(): void { vi.stubGlobal( @@ -94,15 +94,6 @@ function installMockFetch(): void { ); } -function _installMockFetchFailure(): void { - vi.stubGlobal( - 'fetch', - vi.fn(async () => { - return new Response(null, { status: 500 }); - }), - ); -} - describe('cli e2e', () => { const cleanup: string[] = []; @@ -123,7 +114,7 @@ describe('cli e2e', () => { const outputDir = await mkdtemp(join(tmpdir(), 'mcp-contract-kit-')); cleanup.push(outputDir); const outputPath = join(outputDir, 'report.json'); - const yamlPath = resolve(process.cwd(), 'tests/fixtures/registry-valid.yaml'); + const yamlPath = resolve(process.cwd(), 'fixtures/registry-valid.yaml'); const code = await main([ 'validate-yaml', diff --git a/tests/fixtures/registry-env.yaml b/e2e/fixtures/registry-env.yaml similarity index 100% rename from tests/fixtures/registry-env.yaml rename to e2e/fixtures/registry-env.yaml diff --git a/tests/fixtures/registry-invalid.yaml b/e2e/fixtures/registry-invalid.yaml similarity index 100% rename from tests/fixtures/registry-invalid.yaml rename to e2e/fixtures/registry-invalid.yaml diff --git a/tests/fixtures/registry-multi.yaml b/e2e/fixtures/registry-multi.yaml similarity index 100% rename from tests/fixtures/registry-multi.yaml rename to e2e/fixtures/registry-multi.yaml diff --git a/tests/fixtures/registry-valid.yaml b/e2e/fixtures/registry-valid.yaml similarity index 100% rename from tests/fixtures/registry-valid.yaml rename to e2e/fixtures/registry-valid.yaml diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000..4d2fad3 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,24 @@ +{ + "name": "@reaatech/mcp-contract-e2e", + "version": "0.1.0", + "private": true, + "description": "End-to-end tests for mcp-contract-kit", + "type": "module", + "scripts": { + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist" + }, + "dependencies": { + "@reaatech/mcp-contract-core": "workspace:*", + "@reaatech/mcp-contract-client": "workspace:*", + "@reaatech/mcp-contract-validators": "workspace:*", + "@reaatech/mcp-contract-reporters": "workspace:*", + "@reaatech/mcp-contract-observability": "workspace:*", + "@reaatech/mcp-contract-cli": "workspace:*" + }, + "devDependencies": { + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 0000000..616e103 --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/e2e/vitest.config.ts b/e2e/vitest.config.ts new file mode 100644 index 0000000..ad6d6df --- /dev/null +++ b/e2e/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + testTimeout: 60000, + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index acbb5ac..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import globals from 'globals'; - -const tsconfigRootDir = path.dirname(fileURLToPath(import.meta.url)); - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, - { - languageOptions: { - globals: { - ...globals.node, - ...globals.es2022, - }, - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - projectService: true, - tsconfigRootDir, - }, - }, - rules: { - 'no-console': 'error', - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^_', - }, - ], - '@typescript-eslint/explicit-function-return-type': [ - 'error', - { - allowExpressions: true, - allowTypedFunctionExpressions: true, - }, - ], - '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/prefer-nullish-coalescing': 'error', - '@typescript-eslint/prefer-optional-chain': 'error', - 'eqeqeq': ['error', 'always'], - 'no-var': 'error', - 'prefer-const': 'error', - 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], - }, - }, - { - ignores: [ - 'dist/**', - 'coverage/**', - 'node_modules/**', - '*.js', - '*.d.ts', - ], - } -); diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 9765c09..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3607 +0,0 @@ -{ - "name": "mcp-contract-kit", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "mcp-contract-kit", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "ajv": "^8.12.0", - "chalk": "^5.3.0", - "commander": "^12.0.0", - "pino": "^10.3.1", - "yaml": "^2.4.0", - "zod": "^4.3.6" - }, - "bin": { - "mcp-contract-kit": "dist/src/cli.js" - }, - "devDependencies": { - "@types/cli-progress": "^3.11.5", - "@types/node": "^25.6.0", - "@types/ws": "^8.5.10", - "@vitest/coverage-v8": "^4.1.5", - "eslint": "^9.0.0", - "globals": "^17.5.0", - "husky": "^9.0.0", - "lint-staged": "^16.4.0", - "prettier": "^3.2.0", - "typescript": "^6.0.3", - "typescript-eslint": "^8.0.0", - "vitest": "^4.1.5" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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, - "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/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "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, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "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, - "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, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", - "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" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", - "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.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", - "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.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "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/cli-progress": { - "version": "3.11.6", - "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz", - "integrity": "sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==", - "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/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.19.0" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", - "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/type-utils": "8.58.2", - "@typescript-eslint/utils": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.58.2", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", - "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", - "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.2", - "@typescript-eslint/types": "^8.58.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", - "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", - "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", - "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/utils": "8.58.2", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", - "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", - "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.58.2", - "@typescript-eslint/tsconfig-utils": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/visitor-keys": "8.58.2", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", - "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.2", - "@typescript-eslint/types": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", - "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.2", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", - "integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.5", - "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.5", - "vitest": "4.1.5" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.5", - "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.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.5", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "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==", - "dev": true, - "license": "Python-2.0" - }, - "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/ast-v8-to-istanbul": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", - "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "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, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "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, - "license": "MIT" - }, - "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/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "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/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "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/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, - "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/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-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "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" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/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/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", - "dev": true, - "license": "MIT", - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "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", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "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", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "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/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" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "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-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "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", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "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" - } - }, - "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/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/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/lint-staged": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", - "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^14.0.3", - "listr2": "^9.0.5", - "picomatch": "^4.0.3", - "string-argv": "^0.3.2", - "tinyexec": "^1.0.4", - "yaml": "^2.8.2" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": ">=20.17" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@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/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "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/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "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" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pino": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", - "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^4.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", - "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", - "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" - }, - "node_modules/postcss": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", - "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", - "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.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.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-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" - } - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/sonic-boom": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", - "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "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", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "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/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-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/thread-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", - "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - }, - "engines": { - "node": ">=20" - } - }, - "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.1.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", - "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/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/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/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "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 - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.58.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", - "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.2", - "@typescript-eslint/parser": "8.58.2", - "@typescript-eslint/typescript-estree": "8.58.2", - "@typescript-eslint/utils": "8.58.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", - "dev": true, - "license": "MIT" - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", - "tinyglobby": "^0.2.16" - }, - "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.0", - "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_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "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.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", - "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 - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "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", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json index ccb0380..8ffd9e6 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,21 @@ { "name": "mcp-contract-kit", - "version": "1.0.0", - "description": "Conformance test suite for MCP servers. Validate registry compliance, protocol conformance, and routing correctness.", - "author": "Rick Somers (https://reaatech.com)", - "license": "MIT", + "version": "0.1.0", + "private": true, + "description": "Conformance test suite for MCP servers — monorepo with packages for validators, reporters, and CLI", "type": "module", - "main": "dist/src/index.js", - "types": "dist/src/index.d.ts", - "bin": { - "mcp-contract-kit": "dist/src/cli.js" - }, - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "import": "./dist/src/index.js" - } + "scripts": { + "build": "turbo run build", + "test": "turbo run test", + "test:coverage": "turbo run test:coverage", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "format": "biome format --write .", + "typecheck": "tsc --noEmit -p tsconfig.typecheck.json", + "clean": "turbo run clean && rm -rf node_modules", + "changeset": "changeset", + "version-packages": "changeset version", + "release": "turbo run build && changeset publish" }, "keywords": [ "mcp", @@ -26,59 +27,28 @@ "agent", "orchestrator" ], - "scripts": { - "build": "tsc", - "clean": "rm -rf dist coverage", - "dev": "npm run build && node --enable-source-maps dist/src/cli.js", - "lint": "eslint src tests scripts", - "audit": "npm audit --omit=dev", - "format": "prettier --write src tests scripts", - "format:check": "prettier --check src tests scripts", - "generate-report": "node --enable-source-maps dist/scripts/generate-report.js", - "typecheck": "tsc --noEmit", - "test": "vitest run", - "test:ci": "vitest run --coverage", - "test:watch": "vitest", - "test:coverage": "vitest run --coverage", - "prepare": "husky", - "precommit": "lint-staged" - }, - "dependencies": { - "ajv": "^8.12.0", - "chalk": "^5.3.0", - "commander": "^12.0.0", - "pino": "^10.3.1", - "yaml": "^2.4.0", - "zod": "^4.3.6" - }, - "devDependencies": { - "@types/cli-progress": "^3.11.5", - "@types/node": "^25.6.0", - "@types/ws": "^8.5.10", - "@vitest/coverage-v8": "^4.1.5", - "eslint": "^9.0.0", - "globals": "^17.5.0", - "husky": "^9.0.0", - "lint-staged": "^16.4.0", - "prettier": "^3.2.0", - "typescript": "^6.0.3", - "typescript-eslint": "^8.0.0", - "vitest": "^4.1.5" - }, - "engines": { - "node": ">=22.0.0" - }, + "author": "Rick Somers (https://reaatech.com)", + "license": "MIT", + "packageManager": "pnpm@10.22.0", "repository": { "type": "git", "url": "https://github.com/reaatech/mcp-contract-kit.git" }, - "homepage": "https://github.com/reaatech/mcp-contract-kit#readme", "bugs": { "url": "https://github.com/reaatech/mcp-contract-kit/issues" }, - "files": [ - "dist/src", - "LICENSE", - "README.md" - ] + "homepage": "https://github.com/reaatech/mcp-contract-kit#readme", + "pnpm": { + "onlyBuiltDependencies": ["@biomejs/biome", "esbuild"] + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@changesets/changelog-github": "^0.6.0", + "@changesets/cli": "^2.28.1", + "@types/node": "^22.7.0", + "@vitest/coverage-v8": "^3.1.1", + "turbo": "^2.5.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } } diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..3597e52 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,76 @@ +# @reaatech/mcp-contract-cli + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-cli)](https://www.npmjs.com/package/@reaatech/mcp-contract-cli) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml) + +> **Status:** Pre-1.0 + +CLI tool and public API for MCP contract conformance testing. Test MCP servers against protocol, registry, routing, security, and performance validators. + +## Installation + +```bash +npm install -g @reaatech/mcp-contract-cli +# or +pnpm add @reaatech/mcp-contract-cli +``` + +## Feature Overview + +- Five test suites: protocol, registry, routing, security, performance +- Multiple output formats: console, JSON, Markdown, HTML +- Programmatic API for CI/CD integration +- Registry YAML validation with invariant checks +- Customizable severity thresholds and retry policies + +## Quick Start + +### CLI + +```bash +mcp-contract-kit test http://localhost:8080 +mcp-contract-kit test http://localhost:8080 --suite protocol --format json --output report.json +mcp-contract-kit validate-yaml ./agents/my-agent.yaml --strict +``` + +### Programmatic API + +```typescript +import { runTests, validateRegistry, generateReport } from "@reaatech/mcp-contract-cli"; + +const report = await runTests({ endpoint: "http://localhost:8080" }); +const html = await generateReport(report, "html"); +``` + +## API Reference + +### Runner Functions + +- `runTests(options: RunOptions): Promise` +- `validateRegistry(options): Promise` +- `validateProtocol(options: RunOptions): Promise` +- `validateRouting(options: RunOptions): Promise` +- `generateReport(report, format): Promise` + +### CLI + +- `main(argv?)` — CLI entry point +- `parseArgs(args)` — argument parser +- `printHelp()` — help text + +### Re-exports + +The CLI package re-exports from all sibling packages for convenience. + +## Related Packages + +- [`@reaatech/mcp-contract-core`](https://www.npmjs.com/package/@reaatech/mcp-contract-core) — Core domain types, JSON-RPC 2.0 schemas, and utilities +- [`@reaatech/mcp-contract-client`](https://www.npmjs.com/package/@reaatech/mcp-contract-client) — MCP client SDK for connecting to MCP servers +- [`@reaatech/mcp-contract-validators`](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) — Conformance validators +- [`@reaatech/mcp-contract-reporters`](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) — Report formatters +- [`@reaatech/mcp-contract-observability`](https://www.npmjs.com/package/@reaatech/mcp-contract-observability) — Structured logging, metrics, and tracing + +## License + +MIT diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..f91a7f6 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,55 @@ +{ + "name": "@reaatech/mcp-contract-cli", + "version": "0.1.0", + "description": "CLI tool and public API for MCP contract conformance testing", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/cli" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/cli#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "bin": { + "mcp-contract-kit": "./dist/cli.js" + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@reaatech/mcp-contract-core": "workspace:*", + "@reaatech/mcp-contract-client": "workspace:*", + "@reaatech/mcp-contract-validators": "workspace:*", + "@reaatech/mcp-contract-reporters": "workspace:*", + "@reaatech/mcp-contract-observability": "workspace:*", + "commander": "^12.0.0" + }, + "devDependencies": { + "@types/node": "^22.7.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts new file mode 100644 index 0000000..d851323 --- /dev/null +++ b/packages/cli/src/cli.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +/** + * CLI entry point for mcp-contract-kit. + */ + +import { main } from './main.js'; + +main() + .then((code) => { + process.exit(code); + }) + .catch((error) => { + process.stderr.write(`Error: ${(error as Error).message}\n`); + process.exit(3); + }); diff --git a/src/cli/commands/test.command.ts b/packages/cli/src/commands/test.command.ts similarity index 80% rename from src/cli/commands/test.command.ts rename to packages/cli/src/commands/test.command.ts index 448a14c..357b503 100644 --- a/src/cli/commands/test.command.ts +++ b/packages/cli/src/commands/test.command.ts @@ -3,10 +3,10 @@ */ import { writeFile } from 'node:fs/promises'; -import { formatReport } from '../../reporters/index.js'; -import { printConsoleReport } from '../../reporters/console.reporter.js'; -import { runTests } from '../../runner.js'; -import { ParsedCliArgs } from '../config.js'; +import { formatReport } from '@reaatech/mcp-contract-reporters'; +import { printConsoleReport } from '@reaatech/mcp-contract-reporters'; +import type { ParsedCliArgs } from '../config.js'; +import { runTests } from '../runner.js'; export async function runTestCommand(options: ParsedCliArgs): Promise { if (!options.target) { diff --git a/src/cli/commands/validate-yaml.command.ts b/packages/cli/src/commands/validate-yaml.command.ts similarity index 74% rename from src/cli/commands/validate-yaml.command.ts rename to packages/cli/src/commands/validate-yaml.command.ts index 42f5d38..ec0bad2 100644 --- a/src/cli/commands/validate-yaml.command.ts +++ b/packages/cli/src/commands/validate-yaml.command.ts @@ -3,10 +3,10 @@ */ import { writeFile } from 'node:fs/promises'; -import { formatReport } from '../../reporters/index.js'; -import { printConsoleReport } from '../../reporters/console.reporter.js'; -import { validateRegistry } from '../../runner.js'; -import { ParsedCliArgs } from '../config.js'; +import { formatReport } from '@reaatech/mcp-contract-reporters'; +import { printConsoleReport } from '@reaatech/mcp-contract-reporters'; +import type { ParsedCliArgs } from '../config.js'; +import { validateRegistry } from '../runner.js'; export async function runValidateYamlCommand(options: ParsedCliArgs): Promise { if (!options.target) { diff --git a/src/cli/config.ts b/packages/cli/src/config.ts similarity index 95% rename from src/cli/config.ts rename to packages/cli/src/config.ts index ea3c752..ece6374 100644 --- a/src/cli/config.ts +++ b/packages/cli/src/config.ts @@ -2,9 +2,9 @@ * CLI parsing and defaults. */ -import { ReportFormat } from '../reporters/index.js'; -import { Severity, TestCategory } from '../types/domain.js'; -import { getVersion } from '../version.js'; +import { Severity, TestCategory } from '@reaatech/mcp-contract-core'; +import type { ReportFormat } from '@reaatech/mcp-contract-reporters'; +import { getVersion } from './version.js'; export interface ParsedCliArgs { command?: 'test' | 'validate-yaml'; diff --git a/src/index.ts b/packages/cli/src/index.ts similarity index 58% rename from src/index.ts rename to packages/cli/src/index.ts index 95454f6..8e56a2e 100644 --- a/src/index.ts +++ b/packages/cli/src/index.ts @@ -4,6 +4,11 @@ * Conformance test suite for MCP servers. */ +// CLI internals (exposed for e2e testing) +export { main } from './main.js'; +export { parseArgs, printHelp, CLI_VERSION } from './config.js'; +export type { ParsedCliArgs } from './config.js'; + // Runner export { runTests, @@ -17,12 +22,14 @@ export type { RunOptions } from './runner.js'; // Domain types export { TestSuite, + Severity, + TestCategory, +} from '@reaatech/mcp-contract-core'; +export type { TestResult, TestReport, Validator, ValidationContext, - Severity, - TestCategory, ValidationError, MCPClient, MCPRequest, @@ -30,7 +37,7 @@ export { MCPError, ToolDefinition, ToolResult, -} from './types/domain.js'; +} from '@reaatech/mcp-contract-core'; // Schemas export { @@ -42,7 +49,7 @@ export { AgentResponseContractSchema, type AgentConfig, type AgentType, -} from './types/schemas.js'; +} from '@reaatech/mcp-contract-core'; // Reporters export { @@ -52,16 +59,22 @@ export { formatMarkdownReport, formatReport, type ReportFormat, -} from './reporters/index.js'; +} from '@reaatech/mcp-contract-reporters'; // Validators -export * from './validators/index.js'; +export * from '@reaatech/mcp-contract-validators'; // MCP Client -export { MCPHttpClient, createMCPClient } from './mcp-client/index.js'; +export { MCPHttpClient, createMCPClient } from '@reaatech/mcp-contract-client'; // Utilities -export { generateUUID, generateId, now, isValidURL, isPrivateURL } from './utils/index.js'; +export { + generateUUID, + generateId, + now, + isValidURL, + isPrivateURL, +} from '@reaatech/mcp-contract-core'; // Observability export { @@ -78,8 +91,8 @@ export { withSpan, toTraceParent, fromTraceParent, -} from './observability/index.js'; -export type { Span, SpanContext } from './observability/index.js'; +} from '@reaatech/mcp-contract-observability'; +export type { Span, SpanContext } from '@reaatech/mcp-contract-observability'; // HTML Reporter -export { generateHtmlReport } from './reporters/index.js'; +export { generateHtmlReport } from '@reaatech/mcp-contract-reporters'; diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts new file mode 100644 index 0000000..8437678 --- /dev/null +++ b/packages/cli/src/main.ts @@ -0,0 +1,33 @@ +/** + * CLI main function and argument parsing. + */ + +import { runTestCommand } from './commands/test.command.js'; +import { runValidateYamlCommand } from './commands/validate-yaml.command.js'; +import { CLI_VERSION, parseArgs, printHelp } from './config.js'; + +export async function main(argv: string[] = process.argv.slice(2)): Promise { + const args = parseArgs(argv); + + if (args.help) { + process.stdout.write(`${printHelp()}\n`); + return 0; + } + + if (args.version) { + process.stdout.write(`mcp-contract-kit v${CLI_VERSION}\n`); + return 0; + } + + if (!args.command) { + process.stderr.write(`${printHelp()}\n`); + return 1; + } + + switch (args.command) { + case 'test': + return runTestCommand(args); + case 'validate-yaml': + return runValidateYamlCommand(args); + } +} diff --git a/src/runner.ts b/packages/cli/src/runner.ts similarity index 93% rename from src/runner.ts rename to packages/cli/src/runner.ts index 639d1c8..77ab0be 100644 --- a/src/runner.ts +++ b/packages/cli/src/runner.ts @@ -2,29 +2,27 @@ * Test runner orchestrator. */ -import { formatReport, ReportFormat } from './reporters/index.js'; -import { createMCPClient } from './mcp-client/index.js'; -import { MetricNames, metrics } from './observability/index.js'; -import { +import { createMCPClient } from '@reaatech/mcp-contract-client'; +import { Severity, TestCategory, TestSuite, generateUUID } from '@reaatech/mcp-contract-core'; +import type { MCPClient, - Severity, - TestCategory, TestReport, TestResult, - TestSuite, ValidationContext, Validator, - generateUUID, -} from './types/domain.js'; -import { now } from './utils/index.js'; -import { getVersion } from './version.js'; +} from '@reaatech/mcp-contract-core'; +import { now } from '@reaatech/mcp-contract-core'; +import { MetricNames, metrics } from '@reaatech/mcp-contract-observability'; +import { formatReport } from '@reaatech/mcp-contract-reporters'; +import type { ReportFormat } from '@reaatech/mcp-contract-reporters'; import { getPerformanceValidators, getProtocolValidators, getRegistryValidators, getRoutingValidators, getSecurityValidators, -} from './validators/index.js'; +} from '@reaatech/mcp-contract-validators'; +import { getVersion } from './version.js'; export interface RunOptions { endpoint: string; diff --git a/src/version.ts b/packages/cli/src/version.ts similarity index 84% rename from src/version.ts rename to packages/cli/src/version.ts index 2d7a0b5..d444873 100644 --- a/src/version.ts +++ b/packages/cli/src/version.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { resolve, dirname } from 'node:path'; let _version: string | undefined; @@ -10,15 +10,14 @@ export function getVersion(): string { const candidates = [ resolve(__dirname, '../package.json'), resolve(__dirname, '../../package.json'), + resolve(__dirname, '../../../package.json'), ]; for (const pkgPath of candidates) { try { const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string }; _version = pkg.version; return _version; - } catch { - continue; - } + } catch {} } _version = '0.0.0'; return _version; diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/cli/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/client/LICENSE b/packages/client/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/client/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/client/README.md b/packages/client/README.md new file mode 100644 index 0000000..b736449 --- /dev/null +++ b/packages/client/README.md @@ -0,0 +1,350 @@ +# @reaatech/mcp-contract-client + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-client)](https://www.npmjs.com/package/@reaatech/mcp-contract-client) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![CI](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml) + +> **Status:** Pre-1.0 — APIs may change before the stable release. + +MCP client SDK for connecting to and testing Model Context Protocol (MCP) servers over HTTP. Supports JSON-RPC 2.0 request/response, tool discovery, tool invocation, tracing propagation, and SSE streaming. + +## Installation + +```bash +npm install @reaatech/mcp-contract-client +``` + +```bash +pnpm add @reaatech/mcp-contract-client +``` + +## Feature Overview + +- Full JSON-RPC 2.0 request/response over HTTP +- Factory function (`createMCPClient`) and class API (`MCPHttpClient`) +- Tool discovery via `tools/list` and tool invocation via `tools/call` +- Automatic retry with configurable backoff +- AbortSignal-based request timeouts +- SSE (Server-Sent Events) stream parsing for streaming transports +- Trace context propagation via W3C `traceparent` headers +- Typed request builders for `initialize`, `tools/list`, and `tools/call` +- Composable transport layer (`MCPTransport` interface) + +## Quick Start + +```ts +import { createMCPClient, HttpTransport } from '@reaatech/mcp-contract-client'; + +// Create a client +const client = createMCPClient({ + endpoint: 'http://localhost:8080', + timeout: 10000, + retries: 2, +}); + +// Connect (sends initialize, or falls back to tools/list) +await client.connect(); + +// Discover tools +const tools = await client.listTools(); +console.log(tools.map(t => t.name)); + +// Call a tool +const result = await client.callTool('my_tool', { key: 'value' }); +console.log(result.content); + +// Disconnect +await client.disconnect(); +``` + +## API Reference + +### `createMCPClient(options)` + +Factory function that returns an `MCPClient` instance. + +```ts +function createMCPClient( + options: Omit & { endpoint: string } +): MCPClient; +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `endpoint` | `string` | _(required)_ | URL of the MCP server | +| `timeout` | `number` | _(required)_ | Request timeout in milliseconds | +| `retries` | `number` | _(required)_ | Maximum retry attempts on failure | +| `headers` | `Record` | `undefined` | Additional headers to include with every request | + +`MCPClient` is the interface from `@reaatech/mcp-contract-core`: + +```ts +interface MCPClient { + connect(): Promise; + sendRequest(request: MCPRequest): Promise>; + callTool(name: string, args: Record): Promise; + listTools(): Promise; + disconnect(): Promise; + getSessionId(): Promise; +} +``` + +--- + +### `MCPHttpClient` (class) + +Concrete `MCPClient` implementation that communicates with an MCP server over HTTP. Uses `HttpTransport` internally. + +```ts +import { MCPHttpClient } from '@reaatech/mcp-contract-client'; +``` + +#### Constructor + +```ts +new MCPHttpClient(options: MCPClientOptions) +``` + +`MCPClientOptions`: + +| Field | Type | Description | +|-------|------|-------------| +| `endpoint` | `string` | Server URL | +| `timeout` | `number` | Per-request timeout (ms) | +| `retries` | `number` | Max retry attempts | +| `headers` | `Record` _(optional)_ | Extra HTTP headers | + +#### Instance Methods + +| Method | Returns | Description | +|--------|---------|-------------| +| `connect()` | `Promise` | Sends an `initialize` request (falls back to `tools/list` to verify connectivity). Idempotent — safe to call when already connected. | +| `sendRequest(request)` | `Promise>` | Sends an arbitrary JSON-RPC request and returns the parsed response body. | +| `callTool(name, args)` | `Promise` | Sends a `tools/call` request and returns the tool result (`content` array + `isError` flag). | +| `listTools()` | `Promise` | Sends a `tools/list` request and returns the parsed tool definitions. Throws if the server returns an error. | +| `disconnect()` | `Promise` | Sends a `notifications/terminated` notification and resets internal state. Safe to call when already disconnected. | +| `getSessionId()` | `Promise` | Returns (or generates) a UUID session identifier. | + +--- + +### `HttpTransport` (class) + +Low-level HTTP transport that implements `MCPTransport`. Handles request serialization, response parsing, SSE detection, retries, and timeout. + +```ts +import { HttpTransport } from '@reaatech/mcp-contract-client'; +``` + +#### Constructor + +```ts +new HttpTransport(options: TransportOptions) +``` + +#### TransportOptions + +| Field | Type | Description | +|-------|------|-------------| +| `endpoint` | `string` | Server URL | +| `timeout` | `number` | Request timeout (ms), enforced via `AbortController` | +| `retries` | `number` | Max retry attempts with exponential backoff (base=100ms, max=2000ms) | +| `headers` | `Record` _(optional)_ | Extra HTTP headers (merged with `content-type: application/json`) | + +#### Instance Methods + +| Method | Returns | Description | +|--------|---------|-------------| +| `request(request)` | `Promise>` | Sends a JSON-RPC request via `POST` and returns the full transport response including status code and headers. Automatically handles SSE (`text/event-stream`) and JSON content types. | + +--- + +### `TransportResponse` + +Return value from `MCPTransport.request()`. + +| Field | Type | Description | +|-------|------|-------------| +| `body` | `MCPResponse` | Parsed JSON-RPC response body | +| `status` | `number` | HTTP status code | +| `headers` | `Headers` | Response headers | + +### `MCPTransport` (interface) + +Transport abstraction that can be implemented for custom transports. + +```ts +interface MCPTransport { + request(request: MCPRequest): Promise>; +} +``` + +--- + +### Request Builders + +Convenience functions that construct `MCPRequest` objects conforming to JSON-RPC 2.0. + +#### `buildRequest(method, params?, id?)` + +Constructs a generic JSON-RPC 2.0 request. + +```ts +function buildRequest( + method: string, + params?: Record, + id?: string | number +): MCPRequest; +``` + +#### `buildInitializeRequest(id?)` + +Constructs an `initialize` request with `protocolVersion: "2024-11-05"` and client info. + +```ts +function buildInitializeRequest(id?: string | number): MCPRequest; +``` + +#### `buildListToolsRequest(id?)` + +Constructs a `tools/list` request. + +```ts +function buildListToolsRequest(id?: string | number): MCPRequest; +``` + +#### `buildToolCallRequest(name, args, id?)` + +Constructs a `tools/call` request with the given tool name and arguments. + +```ts +function buildToolCallRequest( + name: string, + args: Record, + id?: string | number +): MCPRequest; +``` + +--- + +### `createTracingHeaders()` + +Generates W3C trace context propagation headers (`traceparent`) from the current OpenTelemetry context. Returns an empty object when no active context is found. + +```ts +function createTracingHeaders(): Record; +``` + +Commonly used to inject distributed tracing headers into `MCPClientOptions.headers`: + +```ts +const client = createMCPClient({ + endpoint: 'http://localhost:8080', + timeout: 10000, + retries: 2, + headers: createTracingHeaders(), +}); +``` + +## Usage Patterns + +### Sending Custom Requests + +Use `sendRequest` with a request builder for any MCP method: + +```ts +import { buildRequest } from '@reaatech/mcp-contract-client'; + +const response = await client.sendRequest( + buildRequest('resources/list', {}, 1) +); +``` + +### Tool Discovery & Invocation + +```ts +const tools = await client.listTools(); + +for (const tool of tools) { + console.log(`${tool.name}: ${tool.description}`); +} + +const result = await client.callTool('greet', { name: 'World' }); +if (result.isError) { + console.error('Tool returned an error'); +} else { + for (const item of result.content) { + if (item.type === 'text') { + console.log(item.text); + } + } +} +``` + +### Retry & Timeout Configuration + +The transport retries on network failures and HTTP errors with exponential backoff. Configure via constructor options: + +```ts +const client = createMCPClient({ + endpoint: 'http://localhost:8080', + timeout: 5000, // 5s per request + retries: 3, // up to 3 retries +}); +``` + +### Using the Transport Directly + +For cases where you need access to response status codes or headers, use `HttpTransport` directly: + +```ts +const transport = new HttpTransport({ + endpoint: 'http://localhost:8080', + timeout: 10000, + retries: 2, +}); + +const { body, status, headers } = await transport.request( + buildInitializeRequest() +); + +console.log(status); // 200 +``` + +## Error Handling + +The client throws on connection failures, timeouts, and transport-level errors: + +```ts +try { + await client.connect(); +} catch (error) { + console.error('Connection failed:', error.message); + // Example: "Failed to connect to MCP server at http://localhost:8080: ..." +} +``` + +JSON-RPC errors appear on the response object, not as thrown exceptions: + +```ts +const response = await client.sendRequest( + buildRequest('tools/call', { name: 'nonexistent', arguments: {} }) +); + +if (response.error) { + console.error(`RPC error ${response.error.code}: ${response.error.message}`); +} +``` + +The `callTool` helper normalizes errors — a JSON-RPC error produces a `ToolResult` with `isError: true` and an empty `content` array. + +## Related Packages + +- [@reaatech/mcp-contract-core](https://www.npmjs.com/package/@reaatech/mcp-contract-core) — Domain types, JSON-RPC 2.0 schemas, and shared utilities +- [@reaatech/mcp-contract-validators](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) — Conformance validators +- [@reaatech/mcp-contract-observability](https://www.npmjs.com/package/@reaatech/mcp-contract-observability) — OpenTelemetry tracing and metrics +- [@reaatech/mcp-contract-cli](https://www.npmjs.com/package/@reaatech/mcp-contract-cli) — CLI tool and programmatic API +- [@reaatech/mcp-contract-reporters](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) — Test result reporters (console, JSON, HTML) + +## License + +MIT diff --git a/packages/client/package.json b/packages/client/package.json new file mode 100644 index 0000000..45a7e0f --- /dev/null +++ b/packages/client/package.json @@ -0,0 +1,48 @@ +{ + "name": "@reaatech/mcp-contract-client", + "version": "0.1.0", + "description": "MCP client SDK for connecting to and testing MCP servers", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/client" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/client#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@reaatech/mcp-contract-core": "workspace:*", + "@reaatech/mcp-contract-observability": "workspace:*" + }, + "devDependencies": { + "@types/node": "^22.7.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/src/mcp-client/client.ts b/packages/client/src/client.ts similarity index 87% rename from src/mcp-client/client.ts rename to packages/client/src/client.ts index 005cb66..5d8e719 100644 --- a/src/mcp-client/client.ts +++ b/packages/client/src/client.ts @@ -3,14 +3,21 @@ * Implements the MCPClient interface for conformance testing */ -import { MCPClient, MCPRequest, MCPResponse, ToolDefinition, ToolResult } from '../types/domain.js'; -import { generateId } from '../utils/index.js'; +import type { + MCPClient, + MCPRequest, + MCPResponse, + ToolDefinition, + ToolResult, +} from '@reaatech/mcp-contract-core'; +import { generateId } from '@reaatech/mcp-contract-core'; import { buildInitializeRequest, buildListToolsRequest, buildToolCallRequest, } from './request-builder.js'; -import { HttpTransport, MCPTransport } from './transport.js'; +import { HttpTransport } from './transport.js'; +import type { MCPTransport } from './transport.js'; interface MCPClientOptions { endpoint: string; @@ -24,9 +31,6 @@ interface MCPClientOptions { */ export class MCPHttpClient implements MCPClient { private readonly endpoint: string; - private readonly timeout: number; - private readonly retries: number; - private readonly headers: Record; private sessionId: string | null = null; private connected = false; private requestId = 0; @@ -34,12 +38,6 @@ export class MCPHttpClient implements MCPClient { constructor(options: MCPClientOptions) { this.endpoint = options.endpoint; - this.timeout = options.timeout; - this.retries = options.retries; - this.headers = { - 'Content-Type': 'application/json', - ...options.headers, - }; this.transport = new HttpTransport(options); } diff --git a/src/mcp-client/index.ts b/packages/client/src/index.ts similarity index 100% rename from src/mcp-client/index.ts rename to packages/client/src/index.ts diff --git a/src/mcp-client/request-builder.ts b/packages/client/src/request-builder.ts similarity index 81% rename from src/mcp-client/request-builder.ts rename to packages/client/src/request-builder.ts index 31033b9..7091e74 100644 --- a/src/mcp-client/request-builder.ts +++ b/packages/client/src/request-builder.ts @@ -2,10 +2,10 @@ * Helpers for constructing MCP JSON-RPC requests and tracing headers. */ -import { MCPRequest } from '../types/domain.js'; -import { getCurrentContext, toTraceParent } from '../observability/tracing.js'; -import { generateUUID } from '../utils/index.js'; -import { getVersion } from '../version.js'; +import type { MCPRequest } from '@reaatech/mcp-contract-core'; +import { generateUUID } from '@reaatech/mcp-contract-core'; +import { getVersion } from '@reaatech/mcp-contract-core'; +import { getCurrentContext, toTraceParent } from '@reaatech/mcp-contract-observability'; export function buildRequest( method: string, diff --git a/src/mcp-client/transport.ts b/packages/client/src/transport.ts similarity index 94% rename from src/mcp-client/transport.ts rename to packages/client/src/transport.ts index 5f9d4a8..653dd52 100644 --- a/src/mcp-client/transport.ts +++ b/packages/client/src/transport.ts @@ -2,8 +2,8 @@ * Transport layer for MCP requests. */ -import { MCPRequest, MCPResponse } from '../types/domain.js'; -import { retry } from '../utils/index.js'; +import type { MCPRequest, MCPResponse } from '@reaatech/mcp-contract-core'; +import { retry } from '@reaatech/mcp-contract-core'; export interface TransportResponse { body: MCPResponse; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/client/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/client/vitest.config.ts b/packages/client/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/client/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/core/LICENSE b/packages/core/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/core/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000..382d91e --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,201 @@ +# @reaatech/mcp-contract-core + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-core)](https://www.npmjs.com/package/@reaatech/mcp-contract-core) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![CI](https://img.shields.io/github/actions/workflow/status/reaatech/mcp-contract-kit/ci.yml?branch=main)](https://github.com/reaatech/mcp-contract-kit/actions) + +> **Status:** Pre-1.0 — APIs may change in minor versions. Pin to a specific version in production. + +Core domain types, JSON-RPC 2.0 schemas, and shared utilities for MCP contract validation. +Provides the foundational enums, interfaces, Zod schemas, and helper functions used across all +`@reaatech/mcp-contract-*` packages. + +## Installation + +```bash +npm install @reaatech/mcp-contract-core +``` + +```bash +pnpm add @reaatech/mcp-contract-core +``` + +## Feature Overview + +- **Enums** — `Severity`, `TestCategory`, and `TestSuite` for categorizing test results +- **Result types** — `TestResult`, `TestReport`, and `ValidationError` interfaces for structured test output +- **Validator contracts** — `Validator` and `ValidationContext` interfaces for implementing custom validators +- **MCP client types** — `MCPClient`, `MCPRequest`, `MCPResponse`, `MCPError`, `ToolDefinition`, `ToolResult` for JSON-RPC 2.0 interaction +- **Zod schemas** — Runtime validation schemas for agent YAML, JSON-RPC messages, tools, and orchestrator contracts +- **Utilities** — UUID generation, retry with backoff, URL validation, statistics, sensitive data redaction, and more + +## Quick Start + +```ts +import { + Severity, + TestCategory, + TestSuite, + AgentConfigSchema, + MCPResponseSchema, +} from '@reaatech/mcp-contract-core'; +import type { + TestResult, + TestReport, + Validator, + AgentConfig, +} from '@reaatech/mcp-contract-core'; + +// Use enums +const severity = Severity.CRITICAL; +const category = TestCategory.PROTOCOL; + +// Create a test result +const result: TestResult = { + validator: 'json-rpc-compliance', + category: TestCategory.PROTOCOL, + passed: false, + severity: Severity.CRITICAL, + message: 'Missing jsonrpc field in response', + remediation: 'Add "jsonrpc": "2.0" to all responses', + durationMs: 5, + timestamp: new Date().toISOString(), +}; + +// Validate with schemas +const parsed = AgentConfigSchema.safeParse({ + agent_id: 'my-agent', + display_name: 'My Agent', + description: 'A test agent', + endpoint: 'https://api.example.com', + type: 'mcp', + is_default: false, + confidence_threshold: 0.8, + clarification_required: true, + examples: ['What is the weather?'], +}); + +if (!parsed.success) { + console.error(parsed.error.flatten()); +} +``` + +## Exports + +### Categories + +| Export | Kind | Description | +|--------|------|-------------| +| `Severity` | enum | `CRITICAL`, `WARNING`, `INFO` — severity levels for test results | +| `TestCategory` | enum | `REGISTRY`, `PROTOCOL`, `ROUTING`, `SECURITY`, `PERFORMANCE` — validator categories | +| `TestSuite` | enum | `REGISTRY`, `PROTOCOL`, `ROUTING`, `SECURITY`, `PERFORMANCE`, `ALL` — test suite identifiers | + +### Results + +| Export | Kind | Description | +|--------|------|-------------| +| `TestResult` | interface | Individual test result with validator, category, passed, severity, message, remediation, details, duration, timestamp | +| `TestReport` | interface | Aggregated test report with id, endpoint, timing, results array, summary, failures, passed flag, error, version | +| `ValidationError` | interface | Validation error with field, message, line, severity, type | + +### Validation + +| Export | Kind | Description | +|--------|------|-------------| +| `Validator` | interface | Validator contract with `name`, `category`, `severity`, `validate()`, and optional `setup()`/`teardown()` | +| `ValidationContext` | interface | Context passed to validators with `client`, `endpoint`, `options`, `requestId`, `artifacts` | + +### Client Types + +| Export | Kind | Description | +|--------|------|-------------| +| `MCPClient` | interface | MCP client interface with `connect()`, `sendRequest()`, `callTool()`, `listTools()`, `disconnect()`, `getSessionId()` | +| `MCPRequest` | interface | JSON-RPC 2.0 request with `jsonrpc`, `method`, `id`, `params` | +| `MCPResponse` | interface | JSON-RPC 2.0 response with `jsonrpc`, `id`, optional `result` or `error` | +| `MCPError` | interface | JSON-RPC 2.0 error with `code`, `message`, `data` | +| `ToolDefinition` | interface | MCP tool definition with `name`, `description`, `inputSchema` | +| `ToolResult` | interface | Tool call result with `content` array and optional `isError` | + +### Config Types + +| Export | Kind | Description | +|--------|------|-------------| +| `TestOptions` | interface | Test execution options with `timeout`, `retries`, `failOn`, `verbose`, `suites`, `yamlPath` | + +### Schemas + +| Export | Kind | Description | +|--------|------|-------------| +| `AgentConfigSchema` | ZodObject | Validates agent YAML definition (agent_id, display_name, description, endpoint, type, is_default, confidence_threshold, clarification_required, examples) | +| `MCPRequestSchema` | ZodObject | Validates JSON-RPC 2.0 request structure | +| `MCPErrorSchema` | ZodObject | Validates JSON-RPC 2.0 error structure | +| `MCPResponseSchema` | ZodObject | Validates JSON-RPC 2.0 response (enforces exactly one of result or error) | +| `ToolDefinitionSchema` | ZodObject | Validates MCP tool definition (name, description, inputSchema) | +| `AgentRequestContractSchema` | ZodObject | Validates orchestrator-to-agent request contract | +| `AgentResponseContractSchema` | ZodObject | Validates agent-to-orchestrator response contract | + +### Schema Types + +| Export | Kind | Description | +|--------|------|-------------| +| `AgentConfigInput` | type | Inferred type from `AgentConfigSchema` | +| `AgentConfig` | type | Alias for `AgentConfigInput` | +| `AgentType` | type | String literal type `'mcp'` | +| `ToolDefinitionInput` | type | Inferred type from `ToolDefinitionSchema` | +| `AgentRequestContract` | type | Inferred type from `AgentRequestContractSchema` | +| `AgentResponseContract` | type | Inferred type from `AgentResponseContractSchema` | + +### Utilities + +| Export | Kind | Description | +|--------|------|-------------| +| `generateId()` | function | Generate a unique request ID using timestamp and random string | +| `generateUUID()` | function | Generate a cryptographically random UUID v4 | +| `sleep(ms)` | function | Promise-based sleep for a given number of milliseconds | +| `retry(fn, opts)` | function | Retry a function with exponential backoff | +| `measureTime(fn)` | function | Measure execution time of an async function, returns `{ result, durationMs }` | +| `now()` | function | Get current ISO 8601 timestamp string | +| `truncate(str, maxLength)` | function | Truncate a string to a maximum length, appending `...` if truncated | +| `redactSensitiveData(obj, keys?)` | function | Redact sensitive keys (password, token, secret, key, authorization) from an object | +| `percentile(values, p)` | function | Calculate the p-th percentile from an array of numbers | +| `isValidURL(value)` | function | Check if a value is a valid http/https URL | +| `isPrivateURL(value)` | function | Check if a URL is private/localhost (SSRF protection) | +| `calculateStats(values)` | function | Calculate min, max, mean, p50, p90, p99 from an array of values | +| `isValidUUID(value)` | function | Validate a string against UUID v4 format | +| `getVersion()` | function | Read the package version from the nearest `package.json` | + +## Usage Pattern + +All Zod schemas are paired with inferred types. Use the schema for runtime validation and the +type for compile-time safety: + +```ts +import { AgentConfigSchema, MCPResponseSchema } from '@reaatech/mcp-contract-core'; +import type { AgentConfig, MCPResponse } from '@reaatech/mcp-contract-core'; + +// Runtime validation +function parseAgentConfig(raw: unknown): AgentConfig { + return AgentConfigSchema.parse(raw); +} + +// Type annotation +function handleResponse(response: MCPResponse): string | undefined { + const parsed = MCPResponseSchema.safeParse(response); + if (!parsed.success) { + throw new Error('Invalid JSON-RPC response'); + } + return parsed.data.result; +} +``` + +## Related Packages + +- [@reaatech/mcp-contract-cli](https://www.npmjs.com/package/@reaatech/mcp-contract-cli) — CLI tool and public API for conformance testing +- [@reaatech/mcp-contract-client](https://www.npmjs.com/package/@reaatech/mcp-contract-client) — MCP client SDK +- [@reaatech/mcp-contract-validators](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) — Conformance validators +- [@reaatech/mcp-contract-reporters](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) — Report formatters +- [@reaatech/mcp-contract-observability](https://www.npmjs.com/package/@reaatech/mcp-contract-observability) — Logging, metrics, tracing + +## License + +MIT diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..c123343 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,48 @@ +{ + "name": "@reaatech/mcp-contract-core", + "version": "0.1.0", + "description": "Core domain types, schemas, and utilities for MCP contract validation", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/core" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/core#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "ajv": "^8.12.0", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/node": "^22.7.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/src/types/domain.ts b/packages/core/src/domain.ts similarity index 99% rename from src/types/domain.ts rename to packages/core/src/domain.ts index 04bde9e..94c2fb3 100644 --- a/src/types/domain.ts +++ b/packages/core/src/domain.ts @@ -2,8 +2,6 @@ * Core domain types for mcp-contract-kit */ -export { generateUUID } from '../utils/index.js'; - /** Severity levels for test results */ export enum Severity { /** Critical failures that must be fixed before production */ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..97fc686 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,4 @@ +export * from './domain.js'; +export * from './schemas.js'; +export * from './utils.js'; +export { getVersion } from './version.js'; diff --git a/src/types/schemas.ts b/packages/core/src/schemas.ts similarity index 98% rename from src/types/schemas.ts rename to packages/core/src/schemas.ts index 7139d31..d9e2de6 100644 --- a/src/types/schemas.ts +++ b/packages/core/src/schemas.ts @@ -139,4 +139,4 @@ export function isValidUUID(value: string): boolean { return UUID_REGEX.test(value); } -export { isValidURL, isPrivateURL } from '../utils/index.js'; +export { isValidURL, isPrivateURL } from './utils.js'; diff --git a/src/utils/index.ts b/packages/core/src/utils.ts similarity index 90% rename from src/utils/index.ts rename to packages/core/src/utils.ts index f694625..b5fd5dd 100644 --- a/src/utils/index.ts +++ b/packages/core/src/utils.ts @@ -15,9 +15,9 @@ export function generateId(): string { export function generateUUID(): string { const bytes = crypto.getRandomValues(new Uint8Array(16)); // Set version 4 - bytes[6] = (bytes[6]! & 0x0f) | 0x40; - // Set variant - bytes[8] = (bytes[8]! & 0x3f) | 0x80; + bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40; + // Set version + bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80; const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); @@ -57,7 +57,7 @@ export async function retry( } catch (error) { lastError = error as Error; if (attempt < maxRetries) { - const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs); + const delay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs); await sleep(delay); } } @@ -90,7 +90,7 @@ export function now(): string { */ export function truncate(str: string, maxLength: number): string { if (str.length <= maxLength) return str; - return str.slice(0, maxLength - 3) + '...'; + return `${str.slice(0, maxLength - 3)}...`; } /** @@ -120,9 +120,9 @@ export function percentile(values: number[], p: number): number { const index = (p / 100) * (sorted.length - 1); const lower = Math.floor(index); const upper = Math.ceil(index); - if (lower === upper) return sorted[lower]!; + if (lower === upper) return sorted[lower] ?? 0; const weight = index - lower; - return sorted[lower]! * (1 - weight) + sorted[upper]! * weight; + return (sorted[lower] ?? 0) * (1 - weight) + (sorted[upper] ?? 0) * weight; } /** @@ -183,8 +183,8 @@ export function calculateStats(values: number[]): { const sum = values.reduce((a, b) => a + b, 0); return { - min: sorted[0]!, - max: sorted[sorted.length - 1]!, + min: sorted[0] ?? 0, + max: sorted[sorted.length - 1] ?? 0, mean: sum / values.length, p50: percentile(sorted, 50), p90: percentile(sorted, 90), diff --git a/packages/core/src/version.ts b/packages/core/src/version.ts new file mode 100644 index 0000000..d444873 --- /dev/null +++ b/packages/core/src/version.ts @@ -0,0 +1,24 @@ +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +let _version: string | undefined; + +export function getVersion(): string { + if (_version) return _version; + const __dirname = dirname(fileURLToPath(import.meta.url)); + const candidates = [ + resolve(__dirname, '../package.json'), + resolve(__dirname, '../../package.json'), + resolve(__dirname, '../../../package.json'), + ]; + for (const pkgPath of candidates) { + try { + const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string }; + _version = pkg.version; + return _version; + } catch {} + } + _version = '0.0.0'; + return _version; +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/observability/LICENSE b/packages/observability/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/observability/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/observability/README.md b/packages/observability/README.md new file mode 100644 index 0000000..29adfcd --- /dev/null +++ b/packages/observability/README.md @@ -0,0 +1,376 @@ +# @reaatech/mcp-contract-observability + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-observability)](https://www.npmjs.com/package/@reaatech/mcp-contract-observability) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +> **Status:** Pre-1.0 — API surface is stable but may receive minor changes before the 1.0 release. + +Structured logging (pino), in-memory metrics, and W3C trace context propagation for MCP contract validation. + +## Installation + +```bash +npm install @reaatech/mcp-contract-observability +``` + +## Feature Overview + +| Feature | Capability | +|---------|------------| +| **Structured logging** | pino-based JSON logger with level filtering, ISO timestamps, and automatic PII redaction | +| **Child loggers** | Per-request loggers that inherit level and add `request_id` binding | +| **In-memory metrics** | Counters and duration histograms with label support, plus a `getSummary()` snapshot | +| **Trace context** | Lightweight span creation with W3C traceparent header serialization/parsing | +| **Span helpers** | `withSpan()` wraps async functions in a span with automatic ok/error status | + +## Quick Start + +```ts +import { + logger, + createLogger, + metrics, + MetricNames, + startSpan, + endSpan, + withSpan, + toTraceParent, + fromTraceParent, + getCurrentContext, + getSpans, + clearSpans, +} from '@reaatech/mcp-contract-observability'; +import type { Span, SpanContext } from '@reaatech/mcp-contract-observability'; + +// --- Logging --- +logger.info('Test started', { endpoint: 'http://localhost:8080' }); +logger.warn('High latency detected', { durationMs: 850 }); +logger.error('Connection failed', { error: 'ECONNREFUSED' }); + +const child = logger.child({ request_id: 'abc-123' }); +child.info('Processing request'); + +// --- Metrics --- +metrics.inc(MetricNames.TESTS_TOTAL); +metrics.inc(MetricNames.TESTS_PASSED); +metrics.recordDuration(MetricNames.VALIDATOR_DURATION, 42); + +const summary = metrics.getSummary(); +console.log(summary.counters, summary.histograms); + +// --- Tracing --- +const span = startSpan('protocol-validation'); +endSpan(span, 'ok'); + +const traceparent = toTraceParent(span.context); + +await withSpan('tool-call', async () => { + // child span context propagated automatically +}); +``` + +## API Reference + +### Logging + +#### `logger` + +Default singleton logger instance created with `createLogger()`. Uses `LOG_LEVEL` env var (defaults to `"info"`). + +```ts +import { logger } from '@reaatech/mcp-contract-observability'; + +logger.info(msg, data?); +logger.warn(msg, data?); +logger.error(msg, data?); +logger.debug(msg, data?); +logger.child(bindings); +``` + +Sensitive keys (`password`, `token`, `secret`, `key`, `authorization`, `api_key`, `employee_id`, `session_id`) are automatically redacted to `[REDACTED]` in all log output. + +#### `createLogger(options?)` + +Creates a new pino-backed logger instance. Accepts optional configuration: + +```ts +import { createLogger } from '@reaatech/mcp-contract-observability'; + +const myLogger = createLogger({ + level: 'debug', // default: LOG_LEVEL env var or 'info' + requestId: 'req-456', // optional; injected as request_id on every log line +}); +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `level` | `string` | `LOG_LEVEL` env or `"info"` | pino log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) | +| `requestId` | `string` | — | If set, adds `request_id` binding to every log message | + +Returns an object with `info`, `warn`, `error`, `debug`, and `child` methods. Each log method signature is `(msg: string, data?: Record) => void`. The `child` method returns a new logger that inherits the parent's level. + +### Metrics + +#### `metrics` + +Singleton `MetricsCollector` instance. All counters and histograms are stored in memory and live for the process lifetime. + +```ts +import { metrics, MetricNames } from '@reaatech/mcp-contract-observability'; +``` + +#### `MetricNames` + +Constants for built-in metric names: + +| Constant | Value | +|----------|-------| +| `MetricNames.TESTS_TOTAL` | `"contract_kit_tests_total"` | +| `MetricNames.TESTS_PASSED` | `"contract_kit_tests_passed"` | +| `MetricNames.TESTS_FAILED` | `"contract_kit_tests_failed"` | +| `MetricNames.TESTS_WARNING` | `"contract_kit_tests_warning"` | +| `MetricNames.VALIDATOR_DURATION` | `"contract_kit_validator_duration_ms"` | +| `MetricNames.RUN_DURATION` | `"contract_kit_run_duration_ms"` | +| `MetricNames.ERRORS_TOTAL` | `"contract_kit_errors_total"` | + +#### `metrics.inc(name, value?, labels?)` + +Increments a counter by the given value (default `1`). + +```ts +metrics.inc(MetricNames.TESTS_TOTAL); +metrics.inc(MetricNames.TESTS_PASSED, 1, { suite: 'protocol' }); +metrics.inc('custom_counter', 5); +``` + +#### `metrics.getCounter(name, labels?)` + +Returns the current value of a counter. + +```ts +const passed = metrics.getCounter(MetricNames.TESTS_PASSED); +``` + +#### `metrics.recordDuration(name, durationMs, labels?)` + +Records a duration value (in milliseconds) into a histogram for the named metric. + +```ts +metrics.recordDuration(MetricNames.VALIDATOR_DURATION, 42); +metrics.recordDuration(MetricNames.RUN_DURATION, 1250, { format: 'json' }); +``` + +#### `metrics.getHistogram(name, labels?)` + +Returns histogram data with bucket counts, sum, and total count. + +```ts +const hist = metrics.getHistogram(MetricNames.VALIDATOR_DURATION); +// { buckets: [{ upperBound: 10, count: 1 }, ...], sum: 42, count: 1 } +``` + +Built-in bucket boundaries (ms): `[10, 50, 100, 250, 500, 1000, 2500, 5000, 10000]`. + +#### `metrics.getSummary()` + +Returns a snapshot of all counters, histograms, and process uptime. + +```ts +const summary = metrics.getSummary(); +// { +// uptime: 120, // seconds +// counters: { ... }, +// histograms: { +// 'contract_kit_validator_duration_ms': { +// count: 1, sum: 42, min: 42, max: 42, mean: 42 +// } +// } +// } +``` + +#### `metrics.reset()` + +Clears all counters and histograms and resets the uptime clock. + +```ts +metrics.reset(); +``` + +### Tracing + +#### `startSpan(name, attributes?)` + +Creates a new span, inheriting trace context from the currently active span (if any). Returns the span object and sets it as the active context. + +```ts +const span = startSpan('protocol-validation'); +const childSpan = startSpan('jsonrpc-check', { version: '2.0' }); +``` + +#### `endSpan(span, status?, errorMessage?)` + +Marks a span as complete with a status (`"ok"` | `"error"`, default `"ok"`) and optional error message. + +```ts +endSpan(span, 'ok'); + +try { + // ... +} catch (err) { + endSpan(span, 'error', (err as Error).message); + throw err; +} +``` + +#### `withSpan(name, fn, attributes?)` + +Executes an async function within a span. Returns the function's result. If the function throws, the span is automatically ended with `"error"` status and the error message is recorded before rethrowing. + +```ts +const result = await withSpan('tool-call', async () => { + return await fetch('http://localhost:8080'); +}); +``` + +#### `getCurrentContext()` + +Returns the currently active `SpanContext` or `null` if no span is active. + +```ts +const ctx = getCurrentContext(); +if (ctx) { + console.log(ctx.traceId, ctx.spanId); +} +``` + +#### `setCurrentContext(context)` + +Sets the active span context. Useful for manually propagating trace context across async boundaries or incoming requests. + +```ts +setCurrentContext({ traceId: 'abc...', spanId: 'def...' }); +setCurrentContext(null); // clear context +``` + +#### `toTraceParent(context)` + +Serializes a `SpanContext` into a W3C traceparent header value (`00-{traceId}-{spanId}-01`). + +```ts +const header = toTraceParent(span.context); +// "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" +``` + +#### `fromTraceParent(header)` + +Parses a W3C traceparent header string into a `SpanContext`. Returns `null` if the header is invalid. + +```ts +const ctx = fromTraceParent('00-abc-def-01'); +// { traceId: 'abc', spanId: 'def' } + +const invalid = fromTraceParent('not-valid'); +// null +``` + +#### `getSpans()` + +Returns all recorded spans (for export or debugging). + +```ts +const allSpans = getSpans(); +``` + +#### `clearSpans()` + +Clears all recorded spans and resets the active context. Useful between test runs. + +```ts +clearSpans(); +``` + +## Usage Patterns + +### Structured Context Logging + +```ts +logger.info('Validator started', { + suite: 'protocol', + validator: 'jsonrpc-version', + endpoint: 'http://localhost:8080', +}); +``` + +### Error Logging + +```ts +try { + await validate(); +} catch (err) { + logger.error('Validation failed', { + error: (err as Error).message, + suite: 'protocol', + }); +} +``` + +### Per-Request Child Loggers + +```ts +function handleRequest(requestId: string) { + const log = logger.child({ request_id: requestId }); + log.info('Request received'); + // ... all subsequent logs carry request_id ... +} +``` + +### Metrics with Labels + +```ts +metrics.inc(MetricNames.TESTS_FAILED, 1, { suite: 'protocol' }); +metrics.inc(MetricNames.TESTS_FAILED, 1, { suite: 'security' }); +metrics.recordDuration(MetricNames.VALIDATOR_DURATION, 35, { validator: 'jsonrpc' }); +``` + +### Trace Context Propagation + +```ts +const span = startSpan('test-run'); + +// Propagate via traceparent header +const header = toTraceParent(span.context); +await fetch('http://downstream:8080', { + headers: { traceparent: header }, +}); + +// After receiving an upstream traceparent +const upstreamCtx = fromTraceParent(req.headers.traceparent); +setCurrentContext(upstreamCtx); +const span = startSpan('handle-request'); + +endSpan(span, 'ok'); +``` + +### Nested Spans with withSpan + +```ts +await withSpan('test-run', async () => { + await withSpan('protocol-suite', async () => { + await withSpan('jsonrpc-check', async () => { + // validation logic + }); + }); +}); +``` + +## Related Packages + +- [@reaatech/mcp-contract-core](https://www.npmjs.com/package/@reaatech/mcp-contract-core) — Core types and schemas +- [@reaatech/mcp-contract-client](https://www.npmjs.com/package/@reaatech/mcp-contract-client) — MCP client SDK +- [@reaatech/mcp-contract-validators](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) — Conformance validators +- [@reaatech/mcp-contract-reporters](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) — Report formatters +- [@reaatech/mcp-contract-cli](https://www.npmjs.com/package/@reaatech/mcp-contract-cli) — CLI tool and public API + +## License + +MIT diff --git a/packages/observability/package.json b/packages/observability/package.json new file mode 100644 index 0000000..17039b5 --- /dev/null +++ b/packages/observability/package.json @@ -0,0 +1,46 @@ +{ + "name": "@reaatech/mcp-contract-observability", + "version": "0.1.0", + "description": "Structured logging, metrics, and tracing for MCP contract validation", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/observability" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/observability#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "pino": "^10.3.1" + }, + "devDependencies": { + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/src/observability/index.ts b/packages/observability/src/index.ts similarity index 100% rename from src/observability/index.ts rename to packages/observability/src/index.ts diff --git a/src/observability/logger.ts b/packages/observability/src/logger.ts similarity index 100% rename from src/observability/logger.ts rename to packages/observability/src/logger.ts diff --git a/src/observability/metrics.ts b/packages/observability/src/metrics.ts similarity index 96% rename from src/observability/metrics.ts rename to packages/observability/src/metrics.ts index a98539c..a472463 100644 --- a/src/observability/metrics.ts +++ b/packages/observability/src/metrics.ts @@ -25,7 +25,7 @@ class MetricsCollector { } /** Increment a counter */ - inc(name: string, value: number = 1, labels?: Record): void { + inc(name: string, value = 1, labels?: Record): void { const key = this.makeKey(name, labels); const current = this.counters.get(key) ?? 0; this.counters.set(key, current + value); @@ -43,7 +43,7 @@ class MetricsCollector { if (!this.histograms.has(key)) { this.histograms.set(key, []); } - this.histograms.get(key)!.push(durationMs); + this.histograms.get(key)?.push(durationMs); } /** Get histogram data for a metric */ diff --git a/src/observability/tracing.ts b/packages/observability/src/tracing.ts similarity index 98% rename from src/observability/tracing.ts rename to packages/observability/src/tracing.ts index dc446e7..d77707d 100644 --- a/src/observability/tracing.ts +++ b/packages/observability/src/tracing.ts @@ -20,7 +20,7 @@ export interface Span { } /** Generate a random hex string for trace/span IDs */ -function generateId(length: number = 16): string { +function generateId(length = 16): string { const bytes = crypto.getRandomValues(new Uint8Array(length / 2)); return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); } diff --git a/packages/observability/tsconfig.json b/packages/observability/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/observability/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/observability/vitest.config.ts b/packages/observability/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/observability/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/reporters/LICENSE b/packages/reporters/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/reporters/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/reporters/README.md b/packages/reporters/README.md new file mode 100644 index 0000000..dbdd6ea --- /dev/null +++ b/packages/reporters/README.md @@ -0,0 +1,213 @@ +# @reaatech/mcp-contract-reporters + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-reporters?color=blue)](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) +[![npm downloads](https://img.shields.io/npm/dm/@reaatech/mcp-contract-reporters)](https://www.npmjs.com/package/@reaatech/mcp-contract-reporters) +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.8+-blue)](https://www.typescriptlang.org/) + +> **Status:** Pre-1.0 — API surface may change before the 1.0 release. Pin your +> dependency to a minor version for stability. + +Report formatters for MCP contract validation — console, JSON, Markdown, and +HTML output. Consumes `TestReport` objects produced by `@reaatech/mcp-contract-core` +and renders them in human- or machine-friendly formats. + +## Installation + +```bash +npm install @reaatech/mcp-contract-reporters +``` + +The package ships dual CJS/ESM builds with TypeScript declarations included. + +## Feature Overview + +| Format | Function | Use Case | +|------------|---------------------------|-------------------------------------| +| Console | `printConsoleReport` | Local CLI / terminal output | +| JSON | `formatJsonReport` | CI/CD pipelines, programmatic use | +| Markdown | `formatMarkdownReport` | GitHub issue comments, README docs | +| HTML | `generateHtmlReport` | Browser dashboard, email reports | +| Dispatcher | `formatReport` | Route to any format by string enum | + +All reporters receive a `TestReport` and return a formatted string (HTML and +Markdown reporters also return strings). + +## Quick Start + +```ts +import { runTests, TestSuite } from '@reaatech/mcp-contract-core'; +import { + formatReport, + printConsoleReport, + formatJsonReport, + formatMarkdownReport, + generateHtmlReport, +} from '@reaatech/mcp-contract-reporters'; + +// Produce a report by running conformance tests +const report = await runTests({ + endpoint: 'http://localhost:8080', + suites: [TestSuite.PROTOCOL, TestSuite.ROUTING], + timeout: 30000, +}); + +// Print a colored console report +printConsoleReport(report); + +// Get a machine-readable JSON string +const json = formatJsonReport(report); +await fs.writeFile('report.json', json); + +// Get GitHub-flavored Markdown +const md = formatMarkdownReport(report); +await fs.writeFile('report.md', md); + +// Generate an interactive HTML dashboard +const html = await generateHtmlReport(report); +await fs.writeFile('report.html', html); + +// Or use the dispatcher with a format string +const output = await formatReport(report, 'markdown'); +``` + +## Report Formats + +### Console + +`formatConsoleReport(report)` and `printConsoleReport(report)` render a colored +terminal report using ANSI escape codes. + +- Green / red / yellow severity indicators +- Emoji icons per result (`🔴` `🟡` `🟢`) +- Summary block with pass/fail counts +- Remediation hints printed inline for failing tests +- `printConsoleReport` writes directly to `process.stdout`, `formatConsoleReport` + returns the string + +### JSON + +`formatJsonReport(report)` produces a pretty-printed JSON string (`JSON.stringify` +with 2-space indent). The output is the raw `TestReport` object — ideal for +downstream tooling, CI parsing, or archival. + +### Markdown + +`formatMarkdownReport(report)` generates a GitHub-flavored Markdown document: + +- Summary table (endpoint, timestamp, duration, status, counts) +- Results table with validator name, pass/fail badge, severity, and message +- Dedicated *Failures* section with per-failure remediation blocks +- Footer with link back to the repository + +### HTML + +`generateHtmlReport(report)` returns a self-contained HTML document with embedded +CSS and JavaScript: + +- Summary cards (total, passed, critical, warnings, info) +- Expandable test rows — click any test to reveal its message and remediation +- Colour-coded severity badges +- Print-friendly styles +- No external dependencies; single `async` function returning `Promise` + +## API Reference + +### `formatReport(report, format)` + +Dispatch formatter by enum string. Returns `Promise` (all formats are +resolved synchronously except `html`, but the API is unified under `async`). + +```ts +function formatReport(report: TestReport, format: ReportFormat): Promise +``` + +### `formatConsoleReport(report)` / `printConsoleReport(report)` + +Colored console output. `formatConsoleReport` returns the string; `printConsoleReport` +writes it to stdout and returns `void`. + +```ts +function formatConsoleReport(report: TestReport): string +function printConsoleReport(report: TestReport): void +``` + +### `formatJsonReport(report)` + +Pretty-printed JSON — the raw `TestReport` object serialized with 2-space indentation. + +```ts +function formatJsonReport(report: TestReport): string +``` + +### `formatMarkdownReport(report)` + +GitHub-flavored Markdown with summary tables, result tables, and detailed failure +breakdowns. + +```ts +function formatMarkdownReport(report: TestReport): string +``` + +### `generateHtmlReport(report)` + +Self-contained interactive HTML dashboard. Returns a `Promise` with the +full document. + +```ts +function generateHtmlReport(report: TestReport): Promise +``` + +### `ReportFormat` + +String literal union type for use with `formatReport`. + +```ts +type ReportFormat = 'console' | 'json' | 'markdown' | 'html' +``` + +### `TestReport` (imported from `@reaatech/mcp-contract-core`) + +The input type shared by all reporters: + +```ts +interface TestReport { + id: string + endpoint: string + startedAt: string + completedAt: string + durationMs: number + timestamp: string + results: TestResult[] + summary: { total: number; passed: number; failed: number; warnings: number; critical: number } + failures: { critical: number; warning: number; info: number } + passed: boolean + error?: string + version: string +} + +interface TestResult { + validator: string + category: TestCategory + passed: boolean + severity: Severity + message: string + remediation?: string + details?: Record + durationMs: number + timestamp: string +} +``` + +## Related Packages + +| Package | Description | +|----------------------------------------------------------------|----------------------------------| +| [`@reaatech/mcp-contract-core`](../core) | Domain types, enums, interfaces | +| [`@reaatech/mcp-contract-validators`](../validators) | Test validator implementations | +| [`@reaatech/mcp-contract-skills`](../skills) | Skill definitions & contracts | +| [`@reaatech/mcp-contract-cli`](../cli) | CLI entry point | + +## License + +MIT © [Rick Somers](https://reaatech.com) diff --git a/packages/reporters/package.json b/packages/reporters/package.json new file mode 100644 index 0000000..9b3cd27 --- /dev/null +++ b/packages/reporters/package.json @@ -0,0 +1,47 @@ +{ + "name": "@reaatech/mcp-contract-reporters", + "version": "0.1.0", + "description": "Report formatters for MCP contract validation (console, JSON, markdown, HTML)", + "license": "MIT", + "author": "Rick Somers (https://reaatech.com)", + "repository": { + "type": "git", + "url": "https://github.com/reaatech/mcp-contract-kit.git", + "directory": "packages/reporters" + }, + "homepage": "https://github.com/reaatech/mcp-contract-kit/tree/main/packages/reporters#readme", + "bugs": { + "url": "https://github.com/reaatech/mcp-contract-kit/issues" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup src/index.ts --format cjs,esm --dts --clean", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "clean": "rm -rf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@reaatech/mcp-contract-core": "workspace:*" + }, + "devDependencies": { + "@types/node": "^22.7.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + } +} diff --git a/src/reporters/console.reporter.ts b/packages/reporters/src/console.reporter.ts similarity index 97% rename from src/reporters/console.reporter.ts rename to packages/reporters/src/console.reporter.ts index 3c5da92..768a772 100644 --- a/src/reporters/console.reporter.ts +++ b/packages/reporters/src/console.reporter.ts @@ -2,7 +2,8 @@ * Console reporter - colored terminal output */ -import { TestReport, Severity } from '../types/domain.js'; +import type { TestReport } from '@reaatech/mcp-contract-core'; +import { Severity } from '@reaatech/mcp-contract-core'; const COLORS = { reset: '\x1b[0m', diff --git a/src/reporters/html.reporter.ts b/packages/reporters/src/html.reporter.ts similarity index 98% rename from src/reporters/html.reporter.ts rename to packages/reporters/src/html.reporter.ts index aee2367..7ce943e 100644 --- a/src/reporters/html.reporter.ts +++ b/packages/reporters/src/html.reporter.ts @@ -2,8 +2,8 @@ * HTML Reporter — generates an interactive HTML dashboard for test results */ -import type { TestReport, TestResult } from '../types/domain.js'; -import { Severity } from '../types/domain.js'; +import type { TestReport, TestResult } from '@reaatech/mcp-contract-core'; +import { Severity } from '@reaatech/mcp-contract-core'; /** * Generate an HTML report from test results diff --git a/src/reporters/index.ts b/packages/reporters/src/index.ts similarity index 93% rename from src/reporters/index.ts rename to packages/reporters/src/index.ts index 1e8299c..4666cc0 100644 --- a/src/reporters/index.ts +++ b/packages/reporters/src/index.ts @@ -2,11 +2,11 @@ * Reporters barrel exports */ -import { TestReport } from '../types/domain.js'; +import type { TestReport } from '@reaatech/mcp-contract-core'; import { formatConsoleReport } from './console.reporter.js'; +import { generateHtmlReport } from './html.reporter.js'; import { formatJsonReport } from './json.reporter.js'; import { formatMarkdownReport } from './markdown.reporter.js'; -import { generateHtmlReport } from './html.reporter.js'; export { formatConsoleReport, printConsoleReport } from './console.reporter.js'; export { formatJsonReport } from './json.reporter.js'; @@ -23,7 +23,6 @@ export async function formatReport(report: TestReport, format: ReportFormat): Pr return formatMarkdownReport(report); case 'html': return generateHtmlReport(report); - case 'console': default: return formatConsoleReport(report); } diff --git a/src/reporters/json.reporter.ts b/packages/reporters/src/json.reporter.ts similarity index 71% rename from src/reporters/json.reporter.ts rename to packages/reporters/src/json.reporter.ts index 6a60411..73d61d2 100644 --- a/src/reporters/json.reporter.ts +++ b/packages/reporters/src/json.reporter.ts @@ -2,7 +2,7 @@ * JSON reporter - machine-readable output */ -import { TestReport } from '../types/domain.js'; +import type { TestReport } from '@reaatech/mcp-contract-core'; export function formatJsonReport(report: TestReport): string { return JSON.stringify(report, null, 2); diff --git a/src/reporters/markdown.reporter.ts b/packages/reporters/src/markdown.reporter.ts similarity index 95% rename from src/reporters/markdown.reporter.ts rename to packages/reporters/src/markdown.reporter.ts index ff0de3f..f0f047b 100644 --- a/src/reporters/markdown.reporter.ts +++ b/packages/reporters/src/markdown.reporter.ts @@ -2,7 +2,8 @@ * Markdown reporter - GitHub-flavored markdown output */ -import { TestReport, Severity } from '../types/domain.js'; +import type { TestReport } from '@reaatech/mcp-contract-core'; +import { Severity } from '@reaatech/mcp-contract-core'; function severityBadge(severity: Severity): string { switch (severity) { diff --git a/packages/reporters/tsconfig.json b/packages/reporters/tsconfig.json new file mode 100644 index 0000000..90d76d7 --- /dev/null +++ b/packages/reporters/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/packages/reporters/vitest.config.ts b/packages/reporters/vitest.config.ts new file mode 100644 index 0000000..bc97278 --- /dev/null +++ b/packages/reporters/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + passWithNoTests: true, + include: ['tests/**/*.test.ts'], + coverage: { + reporter: ['text', 'json-summary'], + }, + }, +}); diff --git a/packages/validators/LICENSE b/packages/validators/LICENSE new file mode 100644 index 0000000..1859241 --- /dev/null +++ b/packages/validators/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 mcp-contract-kit contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/validators/README.md b/packages/validators/README.md new file mode 100644 index 0000000..cf0f6eb --- /dev/null +++ b/packages/validators/README.md @@ -0,0 +1,289 @@ +# @reaatech/mcp-contract-validators + +[![npm version](https://img.shields.io/npm/v/@reaatech/mcp-contract-validators.svg)](https://www.npmjs.com/package/@reaatech/mcp-contract-validators) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![CI](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/reaatech/mcp-contract-kit/actions/workflows/ci.yml) + +> **Status:** Pre-1.0 — API may change before the stable release. Use with a lockfile. + +Conformance validators for MCP servers — protocol compliance, registry validation, routing contracts, security posture, and performance baseline. + +--- + +## Installation + +```bash +npm install @reaatech/mcp-contract-validators +# or +yarn add @reaatech/mcp-contract-validators +# or +pnpm add @reaatech/mcp-contract-validators +``` + +--- + +## Feature Overview + +| Suite | What It Checks | Severity | +|-------|----------------|----------| +| **Protocol** | JSON-RPC 2.0 compliance, `tools/list`, `tools/call`, session management | Critical | +| **Registry** | Agent YAML schema, invariants (unique IDs, default agent), env variable syntax | Critical | +| **Routing** | Request/response contract format, `handle_message` tool, cross-scenario compatibility | Critical | +| **Security** | SSRF protection, authentication posture, input sanitization (XSS, prompt injection, SQLi) | Warning | +| **Performance** | Latency percentiles (p50/p90/p99), concurrent request handling, rate limiting | Warning | + +--- + +## Quick Start + +```typescript +import { + getProtocolValidators, + getRegistryValidators, + getRoutingValidators, + getSecurityValidators, + getPerformanceValidators, +} from '@reaatech/mcp-contract-validators'; +import { createMCPClient } from '@reaatech/mcp-contract-client'; + +const client = createMCPClient({ endpoint: 'http://localhost:8080' }); +await client.connect(); + +// Run a single suite +for (const validator of getProtocolValidators()) { + const result = await validator.validate({ + client, + endpoint: 'http://localhost:8080', + options: { timeout: 30000, retries: 2 }, + requestId: crypto.randomUUID(), + }); + console.log(`${result.validator}: ${result.passed ? 'PASS' : 'FAIL'} — ${result.message}`); +} + +await client.disconnect(); +``` + +Or import individual validators directly: + +```typescript +import { + jsonrpcValidator, + toolDiscoveryValidator, + ssrfValidator, + latencyValidator, +} from '@reaatech/mcp-contract-validators'; + +const result = await jsonrpcValidator.validate(context); +``` + +--- + +## Validator Suites + +### Protocol + +Validates MCP JSON-RPC 2.0 specification compliance. + +| Validator | Export | What it checks | +|-----------|--------|----------------| +| **JSON-RPC Validator** | `jsonrpcValidator` | Ensures all responses include `jsonrpc: "2.0"`, matching `id` fields, mutually exclusive `result`/`error`, valid error code ranges, and that unknown methods return proper errors. Sends `tools/list`, `initialize`, and a bogus method to verify end-to-end compliance. | +| **Tool Discovery Validator** | `toolDiscoveryValidator` | Validates the `tools/list` response: checks that at least one tool is exposed, no duplicate tool names, every tool has a valid name (lowercase with underscores/hyphens), a description, and a valid JSON Schema `inputSchema`. | +| **Tool Execution Validator** | `toolExecutionValidator` | Calls tools via `tools/call` with synthesized valid arguments, verifies that unknown tool names return errors, and checks that tools reject invalid arguments per their input schema. Validates response `content` array structure. | +| **Session Validator** | `sessionValidator` | Verifies that session IDs are non-empty, persist across calls and tool invocations, and that separate clients get isolated session identifiers. Recommends UUID-format session IDs. | + +### Registry + +Validates agent YAML configuration files for orchestrator registries. + +| Validator | Export | What it checks | +|-----------|--------|----------------| +| **Schema Validator** | `schemaValidator` | Parses agent YAML files and validates each agent definition against the `AgentConfigSchema`. Ensures all required fields (`agent_id`, `display_name`, `description`, `endpoint`, `type`, `is_default`, `confidence_threshold`, `clarification_required`, `examples`) are present and correctly typed. Re-exports `validateAgentYAML()` for direct programmatic use. | +| **Invariant Validator** | `invariantValidator` | Validates cross-agent invariants: exactly one default agent, default agent has `confidence_threshold: 0`, all `agent_id` values are unique, all endpoints are valid URLs, no private/localhost endpoints (SSRF prevention), and endpoint URLs stay under 2048 characters. Re-exports `validateInvariants()` for direct use. | +| **Env Expansion Validator** | `envExpansionValidator` | Validates `${ENV_VAR}` syntax in YAML files: detects incomplete variable references, invalid variable names (must be uppercase with underscores), undefined environment variables, and circular references between env vars. Re-exports `validateEnvExpansion()` and `extractEnvVars()`. | + +### Routing + +Validates the request/response contracts used between an orchestrator and an agent. + +| Validator | Export | What it checks | +|-----------|--------|----------------| +| **Request Contract Validator** | `requestContractValidator` | Ensures the agent exposes a `handle_message` tool that accepts the standard request format (`session_id`, `request_id`, `employee_id`, `raw_input`, plus optional `display_name`, `intent_summary`, `entities`, `turn_history`, `workflow_state`). Validates against `AgentRequestContractSchema`. | +| **Response Contract Validator** | `responseContractValidator` | Validates that `handle_message` responses conform to the standard response contract: `content` (non-empty string) and `workflow_complete` (boolean), with an optional `workflow_state` object. Validates against `AgentResponseContractSchema`. | +| **Compatibility Validator** | `compatibilityValidator` | End-to-end contract testing across multiple input scenarios: normal text, empty input, long input (1000 chars), unicode/emoji, and special characters (XSS payloads). Verifies consistent error handling across all scenarios. | + +### Security + +Validates the security posture of the MCP server. + +| Validator | Export | What it checks | +|-----------|--------|----------------| +| **SSRF Validator** | `ssrfValidator` | Checks that the endpoint URL is not a private/localhost address, uses HTTPS in production, and is a syntactically valid URL. Flags private IPs and non-HTTPS endpoints as warnings. | +| **Auth Validator** | `authValidator` | Tests whether the server requires authentication by sending a request with an invalid token. Warns if the server accepts unauthenticated requests, and reports positively if 401/Unauthorized responses are returned. | +| **Input Sanitization Validator** | `inputSanitizationValidator` | Sends prompt injection, XSS, and SQL injection patterns to `handle_message` and checks whether the agent echoes back unsanitized payloads. Tests: "Ignore previous instructions" variants, `', - category: TestCategory.PROTOCOL, - passed: true, - severity: Severity.INFO, - message: 'Message with & "quotes"', - details: {}, - durationMs: 100, - timestamp: '2024-01-01T00:00:00Z', - }, - ], - }; - const output = await generateHtmlReport(report); - expect(output).toContain('<script>'); - expect(output).toContain('&'); - expect(output).toContain('"quotes"'); - expect(output).not.toContain('alert("xss")'); - }); - - it('generates report with different formats via formatReport', async () => { - const report = await validateRegistry({ yamlPath: validYaml }); - const jsonOutput = await generateReport(report, 'json'); - expect(jsonOutput).toContain('"results"'); - - const markdownOutput = await generateReport(report, 'markdown'); - expect(markdownOutput).toContain('# MCP Contract Kit'); - - const htmlOutput = await generateReport(report, 'html'); - expect(htmlOutput).toContain(' { - clearSpans(); - metrics.reset(); - const span = startSpan('validator', { suite: 'protocol' }); - endSpan(span, 'ok'); - await withSpan('wrapped', async () => undefined); - metrics.recordDuration('latency', 42, { suite: 'protocol' }); - metrics.inc('counter', 2, { suite: 'protocol' }); - - const traceParent = toTraceParent(span.context); - expect(fromTraceParent(traceParent)).toMatchObject({ - traceId: span.context.traceId, - spanId: span.context.spanId, - }); - expect(getSpans()).toHaveLength(2); - expect(metrics.getCounter('counter', { suite: 'protocol' })).toBe(2); - expect(metrics.getHistogram('latency', { suite: 'protocol' }).count).toBe(1); - expect(metrics.getSummary().counters).toBeDefined(); - }); - - it('creates loggers that redact sensitive fields', () => { - const logger = createLogger({ requestId: 'request-1' }); - expect(typeof logger.info).toBe('function'); - logger.info('test', { password: 'secret', safe: 'value' }); - }); -}); diff --git a/tests/unit/schemas.test.ts b/tests/unit/schemas.test.ts deleted file mode 100644 index feb9e39..0000000 --- a/tests/unit/schemas.test.ts +++ /dev/null @@ -1,287 +0,0 @@ -/** - * Unit tests for Zod schemas - */ - -import { describe, it, expect } from 'vitest'; -import { - AgentConfigSchema, - MCPRequestSchema, - MCPResponseSchema, - ToolDefinitionSchema, - AgentRequestContractSchema, - AgentResponseContractSchema, -} from '../../src/types/schemas.js'; - -function omitKey>( - value: T, - key: keyof T, -): Record { - const copy = { ...value }; - delete copy[key]; - return copy; -} - -describe('schemas', () => { - describe('AgentConfigSchema', () => { - const validAgent = { - agent_id: 'test-agent', - display_name: 'Test Agent', - description: 'A test agent for validation', - endpoint: 'https://example.com/agent', - type: 'mcp' as const, - is_default: false, - confidence_threshold: 0.7, - clarification_required: true, - examples: ['example 1', 'example 2'], - }; - - it('should validate a correct agent config', () => { - const result = AgentConfigSchema.safeParse(validAgent); - expect(result.success).toBe(true); - }); - - it('should reject missing required fields', () => { - const invalid = omitKey(validAgent, 'agent_id'); - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject invalid endpoint URL', () => { - const invalid = { ...validAgent, endpoint: 'not-a-url' }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject non-HTTPS endpoint', () => { - const invalid = { ...validAgent, endpoint: 'http://example.com' }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject invalid type', () => { - const invalid = { ...validAgent, type: 'invalid' }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject confidence_threshold out of range', () => { - const invalid = { ...validAgent, confidence_threshold: 1.5 }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject empty examples', () => { - const invalid = { ...validAgent, examples: [] }; - const result = AgentConfigSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - }); - - describe('MCPRequestSchema', () => { - const validRequest = { - jsonrpc: '2.0' as const, - method: 'tools/list', - id: 1, - }; - - it('should validate a correct request', () => { - const result = MCPRequestSchema.safeParse(validRequest); - expect(result.success).toBe(true); - }); - - it('should accept params', () => { - const withParams = { ...validRequest, params: { name: 'test' } }; - const result = MCPRequestSchema.safeParse(withParams); - expect(result.success).toBe(true); - }); - - it('should reject missing jsonrpc', () => { - const invalid = omitKey(validRequest, 'jsonrpc'); - const result = MCPRequestSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject wrong jsonrpc version', () => { - const invalid = { ...validRequest, jsonrpc: '1.0' }; - const result = MCPRequestSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing method', () => { - const invalid = omitKey(validRequest, 'method'); - const result = MCPRequestSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing id', () => { - const invalid = omitKey(validRequest, 'id'); - const result = MCPRequestSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - }); - - describe('MCPResponseSchema', () => { - const validResultResponse = { - jsonrpc: '2.0' as const, - id: 1, - result: { tools: [] }, - }; - - const validErrorResponse = { - jsonrpc: '2.0' as const, - id: 1, - error: { code: -32600, message: 'Invalid Request' }, - }; - - it('should validate a result response', () => { - const result = MCPResponseSchema.safeParse(validResultResponse); - expect(result.success).toBe(true); - }); - - it('should validate an error response', () => { - const result = MCPResponseSchema.safeParse(validErrorResponse); - expect(result.success).toBe(true); - }); - - it('should reject missing jsonrpc', () => { - const invalid = omitKey(validResultResponse, 'jsonrpc'); - const result = MCPResponseSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing id', () => { - const invalid = omitKey(validResultResponse, 'id'); - const result = MCPResponseSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject both result and error', () => { - const invalid = { - ...validResultResponse, - error: { code: -32600, message: 'Error' }, - }; - const result = MCPResponseSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject neither result nor error', () => { - const invalid = { jsonrpc: '2.0', id: 1 }; - const result = MCPResponseSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - }); - - describe('ToolDefinitionSchema', () => { - const validTool = { - name: 'test-tool', - description: 'A test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - }; - - it('should validate a correct tool definition', () => { - const result = ToolDefinitionSchema.safeParse(validTool); - expect(result.success).toBe(true); - }); - - it('should reject empty name', () => { - const invalid = { ...validTool, name: '' }; - const result = ToolDefinitionSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing inputSchema', () => { - const invalid = omitKey(validTool, 'inputSchema'); - const result = ToolDefinitionSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - }); - - describe('AgentRequestContractSchema', () => { - const validRequest = { - session_id: '550e8400-e29b-41d4-a716-446655440000', - request_id: '660e8400-e29b-41d4-a716-446655440001', - employee_id: 'emp123', - raw_input: 'What is the weather?', - display_name: 'Weather Agent', - intent_summary: 'Get weather info', - }; - - it('should validate a correct request', () => { - const result = AgentRequestContractSchema.safeParse(validRequest); - expect(result.success).toBe(true); - }); - - it('should reject missing required fields', () => { - const invalid = omitKey(validRequest, 'session_id'); - const result = AgentRequestContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject invalid UUID format', () => { - const invalid = { ...validRequest, session_id: 'not-a-uuid' }; - const result = AgentRequestContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject empty raw_input', () => { - const invalid = { ...validRequest, raw_input: '' }; - const result = AgentRequestContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should accept optional fields', () => { - const withOptional = { - ...validRequest, - entities: { location: 'SF' }, - turn_history: [{ role: 'user', content: 'hello' }], - workflow_state: { step: 1 }, - }; - const result = AgentRequestContractSchema.safeParse(withOptional); - expect(result.success).toBe(true); - }); - }); - - describe('AgentResponseContractSchema', () => { - const validResponse = { - content: 'The weather is sunny.', - workflow_complete: true, - }; - - it('should validate a correct response', () => { - const result = AgentResponseContractSchema.safeParse(validResponse); - expect(result.success).toBe(true); - }); - - it('should reject empty content', () => { - const invalid = { ...validResponse, content: '' }; - const result = AgentResponseContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing content', () => { - const invalid = omitKey(validResponse, 'content'); - const result = AgentResponseContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should reject missing workflow_complete', () => { - const invalid = omitKey({ content: 'test', workflow_complete: true }, 'workflow_complete'); - const result = AgentResponseContractSchema.safeParse(invalid); - expect(result.success).toBe(false); - }); - - it('should accept optional fields', () => { - const withOptional = { - ...validResponse, - workflow_state: { next_step: 'done' }, - isError: false, - errorMessage: '', - }; - const result = AgentResponseContractSchema.safeParse(withOptional); - expect(result.success).toBe(true); - }); - }); -}); diff --git a/tests/unit/utils.test.ts b/tests/unit/utils.test.ts deleted file mode 100644 index dfaf4fe..0000000 --- a/tests/unit/utils.test.ts +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Unit tests for utilities - */ - -import { describe, it, expect } from 'vitest'; -import { - generateId, - generateUUID, - sleep, - retry, - measureTime, - now, - truncate, - redactSensitiveData, - percentile, - calculateStats, - isValidURL, - isPrivateURL, -} from '../../src/utils/index.js'; - -describe('utils', () => { - describe('generateId', () => { - it('should generate a unique ID', () => { - const id1 = generateId(); - const id2 = generateId(); - expect(id1).not.toBe(id2); - expect(id1).toMatch(/^\d+-[a-z0-9]+$/); - }); - }); - - describe('generateUUID', () => { - it('should generate a valid UUID v4', () => { - const uuid = generateUUID(); - expect(uuid).toMatch( - /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, - ); - }); - - it('should generate unique UUIDs', () => { - const uuids = new Set([generateUUID(), generateUUID(), generateUUID()]); - expect(uuids.size).toBe(3); - }); - }); - - describe('sleep', () => { - it('should wait for the specified duration', async () => { - const start = Date.now(); - await sleep(50); - const duration = Date.now() - start; - expect(duration).toBeGreaterThanOrEqual(40); - }); - }); - - describe('retry', () => { - it('should return result on first success', async () => { - const result = await retry(() => Promise.resolve('success'), { - maxRetries: 3, - baseDelayMs: 10, - maxDelayMs: 100, - }); - expect(result).toBe('success'); - }); - - it('should retry on failure and succeed', async () => { - let attempts = 0; - const result = await retry( - () => { - attempts++; - if (attempts < 3) { - return Promise.reject(new Error('temporary error')); - } - return Promise.resolve('success'); - }, - { - maxRetries: 3, - baseDelayMs: 10, - maxDelayMs: 100, - }, - ); - expect(result).toBe('success'); - expect(attempts).toBe(3); - }); - - it('should throw after max retries', async () => { - await expect( - retry(() => Promise.reject(new Error('persistent error')), { - maxRetries: 3, - baseDelayMs: 10, - maxDelayMs: 100, - }), - ).rejects.toThrow('persistent error'); - }); - }); - - describe('measureTime', () => { - it('should measure execution time', async () => { - const { result, durationMs } = await measureTime(async () => { - await sleep(50); - return 'done'; - }); - expect(result).toBe('done'); - expect(durationMs).toBeGreaterThanOrEqual(45); - }); - }); - - describe('now', () => { - it('should return ISO timestamp', () => { - const timestamp = now(); - expect(timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/); - }); - }); - - describe('truncate', () => { - it('should not truncate short strings', () => { - expect(truncate('hello', 10)).toBe('hello'); - }); - - it('should truncate long strings', () => { - expect(truncate('hello world', 8)).toBe('hello...'); - }); - }); - - describe('redactSensitiveData', () => { - it('should redact sensitive keys', () => { - const obj = { password: 'secret', token: 'abc123', name: 'public' }; - const result = redactSensitiveData(obj); - expect(result).toEqual({ - password: '[REDACTED]', - token: '[REDACTED]', - name: 'public', - }); - }); - - it('should accept custom keys', () => { - const obj = { apiKey: 'secret', data: 'public' }; - const result = redactSensitiveData(obj, ['apiKey']); - expect(result).toEqual({ - apiKey: '[REDACTED]', - data: 'public', - }); - }); - }); - - describe('percentile', () => { - it('should calculate percentile correctly', () => { - const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - expect(percentile(values, 50)).toBe(5.5); - expect(percentile(values, 90)).toBe(9.1); - expect(percentile(values, 99)).toBe(9.91); - }); - - it('should return 0 for empty array', () => { - expect(percentile([], 50)).toBe(0); - }); - }); - - describe('calculateStats', () => { - it('should calculate statistics correctly', () => { - const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - const stats = calculateStats(values); - expect(stats.min).toBe(1); - expect(stats.max).toBe(10); - expect(stats.mean).toBe(5.5); - expect(stats.p50).toBe(5.5); - }); - - it('should return zeros for empty array', () => { - const stats = calculateStats([]); - expect(stats).toEqual({ - min: 0, - max: 0, - mean: 0, - p50: 0, - p90: 0, - p99: 0, - }); - }); - }); - - describe('isValidURL', () => { - it('should validate HTTP URLs', () => { - expect(isValidURL('http://example.com')).toBe(true); - }); - - it('should validate HTTPS URLs', () => { - expect(isValidURL('https://example.com')).toBe(true); - }); - - it('should reject invalid URLs', () => { - expect(isValidURL('not-a-url')).toBe(false); - expect(isValidURL('ftp://example.com')).toBe(false); - }); - }); - - describe('isPrivateURL', () => { - it('should detect localhost', () => { - expect(isPrivateURL('https://localhost:8080')).toBe(true); - expect(isPrivateURL('https://127.0.0.1')).toBe(true); - }); - - it('should detect private IP ranges', () => { - expect(isPrivateURL('https://192.168.1.1')).toBe(true); - expect(isPrivateURL('https://10.0.0.1')).toBe(true); - expect(isPrivateURL('https://172.16.0.1')).toBe(true); - }); - - it('should not flag public URLs', () => { - expect(isPrivateURL('https://example.com')).toBe(false); - expect(isPrivateURL('https://8.8.8.8')).toBe(false); - }); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index f7af683..bbd6158 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,41 +4,27 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "lib": ["ES2022"], - "outDir": "./dist", - "rootDir": ".", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, - "removeComments": false, + "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "noImplicitThis": true, - "useUnknownInCatchVariables": true, "alwaysStrict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "exactOptionalPropertyTypes": false, + "noUnusedLocals": true, + "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - "allowUnusedLabels": false, - "allowUnreachableCode": false, - "ignoreDeprecations": "6.0", - "types": ["node"], - "baseUrl": ".", - "paths": { - "@/*": ["src/*"] - } + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true }, - "include": ["src/**/*", "tests/**/*", "scripts/**/*"], - "exclude": ["node_modules", "dist", "coverage"] + "exclude": ["node_modules", "dist", "*.config.js"] } diff --git a/tsconfig.typecheck.json b/tsconfig.typecheck.json new file mode 100644 index 0000000..2463682 --- /dev/null +++ b/tsconfig.typecheck.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@reaatech/mcp-contract-core": ["./packages/core/src/index.ts"], + "@reaatech/mcp-contract-client": ["./packages/client/src/index.ts"], + "@reaatech/mcp-contract-validators": ["./packages/validators/src/index.ts"], + "@reaatech/mcp-contract-reporters": ["./packages/reporters/src/index.ts"], + "@reaatech/mcp-contract-observability": ["./packages/observability/src/index.ts"], + "@reaatech/mcp-contract-cli": ["./packages/cli/src/index.ts"] + } + }, + "include": ["packages/**/*.ts", "e2e/**/*.ts"], + "exclude": ["node_modules", "dist", "**/*.config.ts", "src", "tests", "skills", "scripts", "docs"] +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..cd83e4f --- /dev/null +++ b/turbo.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["**/.env.*local"], + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "test": { + "dependsOn": ["build"] + }, + "test:coverage": { + "dependsOn": ["build"] + }, + "lint": {}, + "typecheck": { + "dependsOn": ["^build"] + }, + "clean": { + "cache": false + } + } +} diff --git a/vitest.config.ts b/vitest.config.ts index ff33568..e30b907 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,38 +1,11 @@ import { defineConfig } from 'vitest/config'; -import path from 'path'; export default defineConfig({ test: { - globals: true, + globals: false, environment: 'node', - include: ['tests/**/*.test.ts'], - exclude: ['node_modules', 'dist', 'coverage'], coverage: { - provider: 'v8', - reporter: ['text', 'json', 'html'], - exclude: [ - 'node_modules', - 'dist', - 'tests', - 'scripts', - '*.config.*', - 'src/types/index.ts', - ], - thresholds: { - global: { - statements: 80, - branches: 80, - functions: 80, - lines: 80, - }, - }, - }, - testTimeout: 30000, - hookTimeout: 30000, - }, - resolve: { - alias: { - '@': path.resolve(__dirname, './src'), + reporter: ['text', 'json-summary'], }, }, });