diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5978552
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+# Dependencies
+node_modules/
+
+# Build output
+dist/
+tsconfig.tsbuildinfo
+
+# Environment
+.env
+.env.local
+.env.*.local
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Logs
+logs/
+*.log
+npm-debug.log*
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..e8416a1
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v24.13.0
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..3d730ef
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,49 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## What This Is
+
+An MCP (Model Context Protocol) server that exposes Bitbucket Cloud Pull Request operations as tools for AI agents. It communicates over stdio using the `@modelcontextprotocol/sdk`.
+
+## Commands
+
+```bash
+npm start # Run server (tsx src/index.ts)
+npm run dev # Run with hot reload (tsx watch)
+npm run build # Compile TypeScript to dist/
+npm test # Run all tests (vitest)
+npm run test:integration # Integration tests only (hits real Bitbucket API)
+vitest run tests/unit/config.test.ts # Run a single test file
+```
+
+Node version: 24.13.0 (see .nvmrc)
+
+## Architecture
+
+The server is a 3-layer pipeline: **MCP protocol** -> **Router** -> **Bitbucket Client**.
+
+- `src/index.ts` — MCP server setup. Registers tool schemas (inputSchema definitions) and delegates `CallToolRequest` to the router. This is where new tools must be declared.
+- `src/router.ts` — `BitbucketRouter` maps tool names to client methods. Handles parameter extraction (supports both `pullRequestId` and `pr_id` forms) and wraps results in `ToolResult { success, data?, error? }`.
+- `src/bitbucket-client.ts` — Axios-based HTTP client against `api.bitbucket.org/2.0`. Uses Basic Auth (email + app password). Has async initialization with a polling `ensureInitialized()` guard on every public method.
+- `src/config.ts` — Credential loading with priority: env vars (`BITBUCKET_MCP_EMAIL`, `BITBUCKET_MCP_TOKEN`) > `.env` file > interactive prompt. Also loads `DEFAULT_WORKSPACE` and `DEFAULT_REPO` for optional parameter defaults.
+
+## Adding a New Tool
+
+1. Add the tool schema in `src/index.ts` (in the `ListToolsRequestSchema` handler array)
+2. Add a case in `BitbucketRouter.executeTool()` switch statement
+3. Add the private handler method in the router
+4. Add the underlying API call in `BitbucketClient`
+
+## Environment Variables
+
+| Variable | Purpose |
+|----------|---------|
+| `BITBUCKET_MCP_EMAIL` | Bitbucket account email (Basic Auth username) |
+| `BITBUCKET_MCP_TOKEN` | Bitbucket app password (Basic Auth password) |
+| `DEFAULT_WORKSPACE` | Optional default workspace slug |
+| `DEFAULT_REPO` | Optional default repository slug |
+
+## Testing
+
+Tests use vitest with 30s timeouts. Unit tests mock the Bitbucket client; integration tests call the real API and require valid credentials in the environment.
diff --git a/README.md b/README.md
index ff0e8f9..5016891 100644
--- a/README.md
+++ b/README.md
@@ -1,162 +1,264 @@
+
+
# Bitbucket MCP Server
-MCP (Model Context Protocol) server for AI agents to interact with **Bitbucket Cloud Pull Requests**.
+**A Model Context Protocol server for AI agents to interact with Bitbucket Cloud Pull Requests**
-## Features
+[](https://nodejs.org/)
+[](https://www.typescriptlang.org/)
+[](https://modelcontextprotocol.io/)
+[](LICENSE)
-- ✅ List pull requests in repositories
-- ✅ Get detailed PR information
-- ✅ Get PR activities/events
-- ✅ Get PR changes (files modified)
-- ✅ Get PR comments and individual comments
-- ✅ Get PR commits
-- ✅ Get PR diff and patch
-- ✅ Get PR participants and reviewers
-- ✅ Get PR status/state
-- ✅ Get PR tasks
-- ✅ Flexible credential configuration (env var or .env file)
-- ✅ Rate limiting protection
-- ✅ Error handling and retry logic
+
-## Installation
+---
+
+## Overview
+
+This MCP server exposes **read-only** Bitbucket Cloud Pull Request operations as tools that AI agents (Claude, etc.) can invoke over stdio. It connects your AI workflow directly to your Bitbucket repositories.
+
+### Capabilities
+
+| Category | Operations |
+|----------|-----------|
+| **Pull Requests** | List, get details, get full expanded PR |
+| **Code Review** | Diff, patch, file changes, commits |
+| **Collaboration** | Comments, activities, participants, reviewers |
+| **Workflow** | Status, tasks, task count |
+| **Auth** | Token validation |
+
+---
+
+## Quick Start
+
+### Prerequisites
+
+- Node.js **24.13+** (see `.nvmrc`)
+- A Bitbucket Cloud [App Password](https://bitbucket.org/account/settings/app-passwords/) with **repository read** permissions
+
+### Installation
```bash
+git clone
+cd bitbucket-mcp
npm install
```
-## Configuration
+### Configuration
-### Credentials Sources (Priority Order)
+Create a `.env` file in the project root:
-1. **Environment Variables** (highest priority):
- ```bash
- export BITBUCKET_MCP_EMAIL=your_email@example.com
- export BITBUCKET_MCP_TOKEN=your_token_here
- ```
+```env
+BITBUCKET_MCP_EMAIL=your_email@example.com
+BITBUCKET_MCP_TOKEN=your_app_password
-2. **.env file** in project root:
- ```
- BITBUCKET_MCP_EMAIL=your_email@example.com
- BITBUCKET_MCP_TOKEN=your_token_here
- ```
+# Optional defaults (avoids passing workspace/repo on every call)
+DEFAULT_WORKSPACE=my-workspace
+DEFAULT_REPO=my-repo
+```
-3. **Interactive prompt** (development fallback)
-
-### Getting Your Access Token
-
-1. Go to [Bitbucket Applications](https://bitbucket.org/account/applications/)
-2. Click "New application" or select existing
-3. Set callback URL to `http://localhost:8080` (or any placeholder)
-4. Copy the **Access token**
-
-**Note:** Bitbucket uses Basic Authentication with your email as username and the access token as password.
-
-## Usage
-
-### Start the Server
+Or export as environment variables (takes priority over `.env`):
```bash
+export BITBUCKET_MCP_EMAIL=your_email@example.com
+export BITBUCKET_MCP_TOKEN=your_app_password
+```
+
+### Run
+
+```bash
+# Production
npm start
-# Or for development:
+
+# Development (hot reload)
npm run dev
+
+# Build
+npm run build
```
-The server will output:
-```
-✅ Bitbucket MCP Server started
- Name: bitbucket-pullrequests v1.0.0
- Token source: environment variable
+---
+
+## MCP Client Configuration
+
+### Claude Code
+
+Add the following to your Claude Code MCP settings (`~/.claude/settings.json` or project `.claude/settings.json`):
+
+```json
+{
+ "mcpServers": {
+ "bitbucket": {
+ "command": "node",
+ "args": [
+ "/absolute/path/to/bitbucket-mcp/dist/index.js"
+ ],
+ "env": {
+ "BITBUCKET_MCP_EMAIL": "your_email@example.com",
+ "BITBUCKET_MCP_TOKEN": "your_bitbucket_app_password",
+ "DEFAULT_WORKSPACE": "your-workspace",
+ "DEFAULT_REPO": "your-repo"
+ }
+ }
+ }
+}
```
-### Available Tools
+### Claude Desktop
-| Tool | Description | Parameters |
-|------|-------------|------------|
-| `list_pull_requests` | List PRs in a repository | workspace, repository, state (optional), author (optional) |
-| `get_pull_request` | Get PR details | workspace, repository, pullRequestId |
-| `get_pull_request_activities` | Get PR activities/events | workspace, repository, pullRequestId, limit (optional), start (optional) |
-| `get_pull_request_changes` | Get files changed in PR | workspace, repository, pullRequestId, limit (optional), start (optional) |
-| `get_pull_request_comments` | Get all PR comments | workspace, repository, pullRequestId, limit (optional), start (optional) |
-| `get_pull_request_comment` | Get a specific PR comment | workspace, repository, pullRequestId, commentId |
-| `get_pull_request_commits` | Get commits in PR | workspace, repository, pullRequestId, limit (optional), start (optional) |
-| `get_pull_request_diff` | Get PR diff | workspace, repository, pullRequestId, path (optional), context (optional) |
-| `get_pull_request_patch` | Get PR patch | workspace, repository, pullRequestId |
-| `get_pull_request_participants` | Get PR participants | workspace, repository, pullRequestId |
-| `get_pull_request_reviewers` | Get PR reviewers | workspace, repository, pullRequestId |
-| `get_pull_request_status` | Get PR status/state | workspace, repository, pullRequestId |
-| `get_pull_request_tasks` | Get PR tasks | workspace, repository, pullRequestId |
-| `get_pull_request_task_count` | Get PR task count | workspace, repository, pullRequestId |
-| `get_full_pull_request` | Get full PR details | workspace, repository, pullRequestId |
+Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS or `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
-### Example MCP Call
+```json
+{
+ "mcpServers": {
+ "bitbucket": {
+ "command": "node",
+ "args": [
+ "/absolute/path/to/bitbucket-mcp/dist/index.js"
+ ],
+ "env": {
+ "BITBUCKET_MCP_EMAIL": "your_email@example.com",
+ "BITBUCKET_MCP_TOKEN": "your_bitbucket_app_password",
+ "DEFAULT_WORKSPACE": "your-workspace",
+ "DEFAULT_REPO": "your-repo"
+ }
+ }
+ }
+}
+```
+
+> **Note:** You must build the project first (`npm run build`) since the config points to `dist/index.js`. Use the **absolute path** to the compiled entry point. Setting `DEFAULT_WORKSPACE` and `DEFAULT_REPO` is optional but convenient — it lets you omit those parameters from every tool call.
+
+---
+
+## Available Tools
+
+### Authentication
+
+| Tool | Description |
+|------|-------------|
+| `validate_token` | Verify credentials are valid |
+
+### Pull Request Discovery
+
+| Tool | Description | Key Parameters |
+|------|-------------|----------------|
+| `list_pull_requests` | List PRs in a repository | `workspace`, `repository`, `state?`, `author?` |
+| `get_pull_request` | Get PR summary | `workspace`, `repository`, `pullRequestId` |
+| `get_full_pull_request` | Get PR with all fields expanded | `workspace`, `repository`, `pullRequestId` |
+| `get_pull_request_status` | Get PR state (open/merged/declined) | `workspace`, `repository`, `pullRequestId` |
+
+### Code Changes
+
+| Tool | Description | Key Parameters |
+|------|-------------|----------------|
+| `get_pull_request_diff` | Get unified diff | `workspace`, `repository`, `pullRequestId`, `path?`, `context?` |
+| `get_pull_request_patch` | Get raw patch file | `workspace`, `repository`, `pullRequestId` |
+| `get_pull_request_changes` | List modified files | `workspace`, `repository`, `pullRequestId` |
+| `get_pull_request_commits` | List commits in PR | `workspace`, `repository`, `pullRequestId` |
+
+### Collaboration
+
+| Tool | Description | Key Parameters |
+|------|-------------|----------------|
+| `get_pull_request_comments` | Get all comments | `workspace`, `repository`, `pullRequestId` |
+| `get_pull_request_comment` | Get a specific comment | `workspace`, `repository`, `pullRequestId`, `commentId` |
+| `get_pull_request_activities` | Get activity feed | `workspace`, `repository`, `pullRequestId` |
+| `get_pull_request_participants` | Get all participants | `workspace`, `repository`, `pullRequestId` |
+| `get_pull_request_reviewers` | Get assigned reviewers | `workspace`, `repository`, `pullRequestId` |
+
+### Tasks
+
+| Tool | Description | Key Parameters |
+|------|-------------|----------------|
+| `get_pull_request_tasks` | Get PR tasks | `workspace`, `repository`, `pullRequestId` |
+| `get_pull_request_task_count` | Get task count | `workspace`, `repository`, `pullRequestId` |
+
+> **Pagination:** Tools that return lists support `limit` and `start` parameters.
+
+---
+
+## Example
```json
{
"name": "list_pull_requests",
"arguments": {
- "workspace": "my-workspace",
- "repository": "my-repo",
+ "workspace": "my-team",
+ "repository": "backend-api",
"state": "open"
}
}
```
-## API Reference
+Response:
-### list_pull_requests(workspace, repository, options?)
+```json
+{
+ "pull_requests": [
+ {
+ "id": 42,
+ "title": "feat: add user authentication",
+ "state": "OPEN",
+ "author": { "display_name": "Jane Doe" },
+ "source": { "branch": { "name": "feature/auth" } },
+ "destination": { "branch": { "name": "main" } }
+ }
+ ],
+ "count": 1
+}
+```
-List pull requests in a repository.
-
-**Options:**
-- `state`: 'open' | 'closed' | 'all' (default: open)
-- `author`: Filter by author username
-
-**Returns:** Array of pull request objects with metadata.
-
-### get_pull_request(workspace, repository, pullRequestId)
-
-Get detailed information about a specific pull request.
-
-**Returns:** Complete PR object including title, description, source/destination branches, reviewers, etc.
-
-### merge_pull_request(workspace, repository, pullRequestId, options?)
-
-Merge a pull request into the target branch.
-
-**Options:**
-- `mergeStrategy`: 'merge' | 'squash' | 'fastforward' (default: merge)
-- `deleteSourceBranch`: boolean (default: false)
-
-### create_pull_request(workspace, repository, title, description, sourceBranch, destinationBranch)
-
-Create a new pull request.
-
-**Returns:** Created PR object with ID and URL.
-
-## Error Handling
-
-The server handles the following errors:
-
-| Error | Cause |
-|-------|-------|
-| `AuthenticationFailed` | Invalid or expired token |
-| `RateLimited` | API rate limit exceeded (429) |
-| `RepositoryNotFound` | Workspace/repo doesn't exist |
-| `PRNotFound` | Pull request ID invalid |
+---
## Development
```bash
-# Install dependencies
-npm install
+# Run tests
+npm test
-# Run in development mode with hot reload
-npm run dev
+# Run tests in watch mode
+npm run test:watch
-# Build for production
-npm run build
+# Integration tests (requires valid credentials)
+npm run test:integration
+
+# Type check
+npx tsc --noEmit
+
+# Coverage report
+npm run test:coverage
```
+### Project Structure
+
+```
+src/
+├── index.ts # MCP server & tool schema definitions
+├── router.ts # Tool name → API method dispatcher
+├── bitbucket-client.ts # Axios HTTP client for Bitbucket Cloud API
+└── config.ts # Credential & default config loading
+tests/
+├── unit/ # Mocked unit tests
+└── integration/ # Live API tests
+```
+
+---
+
+## Authentication Details
+
+This server uses **HTTP Basic Authentication** with the Bitbucket Cloud REST API v2.0:
+
+- **Username** = your Bitbucket email
+- **Password** = an [App Password](https://bitbucket.org/account/settings/app-passwords/)
+
+Credential resolution order:
+1. Environment variables (`BITBUCKET_MCP_EMAIL` + `BITBUCKET_MCP_TOKEN`)
+2. `.env` file in project root
+3. Interactive prompt (development only)
+
+---
+
## License
-MIT
+[MIT](LICENSE)
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..cd46964
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3262 @@
+{
+ "name": "@bitbucket/mcp-server",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@bitbucket/mcp-server",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.29.0",
+ "axios": "^1.6.2",
+ "dotenv": "^16.3.1"
+ },
+ "bin": {
+ "mcp-server": "dist/index.js"
+ },
+ "devDependencies": {
+ "@types/node": "^20.19.39",
+ "@vitest/spy": "^1.6.0",
+ "tsx": "^4.7.0",
+ "typescript": "^5.3.0",
+ "vitest": "^4.1.6"
+ }
+ },
+ "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/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.14",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
+ "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "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/@modelcontextprotocol/sdk": {
+ "version": "1.29.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
+ "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@hono/node-server": "^1.19.9",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.2.1",
+ "hono": "^4.11.4",
+ "jose": "^6.1.3",
+ "json-schema-typed": "^8.0.2",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.25 || ^4.0",
+ "zod-to-json-schema": "^3.25.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
+ "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.130.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz",
+ "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz",
+ "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz",
+ "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz",
+ "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz",
+ "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz",
+ "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz",
+ "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz",
+ "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz",
+ "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz",
+ "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz",
+ "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz",
+ "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz",
+ "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz",
+ "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz",
+ "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz",
+ "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@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/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.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.39",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
+ "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
+ "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/expect/node_modules/@vitest/spy": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
+ "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz",
+ "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.6",
+ "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/mocker/node_modules/@vitest/spy": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
+ "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz",
+ "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz",
+ "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.6",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz",
+ "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz",
+ "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^2.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz",
+ "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "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/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
+ "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.16.0",
+ "form-data": "^4.0.5",
+ "https-proxy-agent": "^5.0.1",
+ "proxy-from-env": "^2.1.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz",
+ "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.5.2",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz",
+ "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.2.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/express/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "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/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.13.7",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
+ "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.12.21",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.21.tgz",
+ "integrity": "sha512-uV63apnb0kyPtAUwoWgaGh9HyIFcv8lgmzPZSiTBQAFOFGIzka5EZ1dZocmGnn0XdX0+XTqJ6Tqv7selMuGLRQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jose": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
+ "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "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-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/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/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/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "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/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "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.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "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-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
+ "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.130.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.1",
+ "@rolldown/binding-darwin-arm64": "1.0.1",
+ "@rolldown/binding-darwin-x64": "1.0.1",
+ "@rolldown/binding-freebsd-x64": "1.0.1",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.1",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.1",
+ "@rolldown/binding-linux-arm64-musl": "1.0.1",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.1",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.1",
+ "@rolldown/binding-linux-x64-gnu": "1.0.1",
+ "@rolldown/binding-linux-x64-musl": "1.0.1",
+ "@rolldown/binding-openharmony-arm64": "1.0.1",
+ "@rolldown/binding-wasm32-wasi": "1.0.1",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.1",
+ "@rolldown/binding-win32-x64-msvc": "1.0.1"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/send/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/send/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "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==",
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/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/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/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/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.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
+ "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
+ "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/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/tinyspy": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
+ "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/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/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.0.13",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
+ "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.14",
+ "rolldown": "1.0.1",
+ "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.18",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz",
+ "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.6",
+ "@vitest/mocker": "4.1.6",
+ "@vitest/pretty-format": "4.1.6",
+ "@vitest/runner": "4.1.6",
+ "@vitest/snapshot": "4.1.6",
+ "@vitest/spy": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "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.6",
+ "@vitest/browser-preview": "4.1.6",
+ "@vitest/browser-webdriverio": "4.1.6",
+ "@vitest/coverage-istanbul": "4.1.6",
+ "@vitest/coverage-v8": "4.1.6",
+ "@vitest/ui": "4.1.6",
+ "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/vitest/node_modules/@vitest/spy": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
+ "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "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/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.2",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
+ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25.28 || ^4"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..54cafd8
--- /dev/null
+++ b/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "@bitbucket/mcp-server",
+ "version": "1.0.0",
+ "description": "MCP Server for Bitbucket Pull Requests - AI agent integration with Bitbucket Cloud REST API",
+ "type": "module",
+ "main": "dist/index.js",
+ "bin": {
+ "@bitbucket/mcp-server": "./dist/index.js"
+ },
+ "scripts": {
+ "build": "tsc && node -e \"const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json')); pkg.main='dist/index.js'; fs.writeFileSync('package.json',JSON.stringify(pkg,null,2));\"",
+ "start": "tsx src/index.ts",
+ "dev": "tsx watch src/index.ts",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:coverage": "vitest run --coverage",
+ "test:integration": "vitest run tests/integration",
+ "test:integration:watch": "vitest tests/integration"
+ },
+ "keywords": [
+ "mcp",
+ "bitbucket",
+ "pull-request",
+ "ai-agent",
+ "atlassian"
+ ],
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.29.0",
+ "axios": "^1.6.2",
+ "dotenv": "^16.3.1"
+ },
+ "devDependencies": {
+ "@types/node": "^20.19.39",
+ "@vitest/spy": "^1.6.0",
+ "tsx": "^4.7.0",
+ "typescript": "^5.3.0",
+ "vitest": "^4.1.6"
+ }
+}
\ No newline at end of file
diff --git a/src/bitbucket-client.ts b/src/bitbucket-client.ts
new file mode 100644
index 0000000..fdfa20a
--- /dev/null
+++ b/src/bitbucket-client.ts
@@ -0,0 +1,612 @@
+/**
+ * Bitbucket API client with token configuration integration.
+ */
+
+import axios, { AxiosInstance, AxiosError } from 'axios';
+import { TokenConfigLoader, TokenConfig } from './config.js';
+
+export interface BitbucketClientOptions {
+ timeout?: number;
+ retryCount?: number;
+}
+
+/**
+ * Bitbucket API client wrapper with authentication and error handling.
+ */
+export class BitbucketClient {
+ private client!: AxiosInstance;
+ private tokenSource: string | null = null;
+ private initialized: boolean = false;
+
+ constructor(options: BitbucketClientOptions = {}) {
+ // Initialize synchronously to avoid race conditions
+ this.initializeClient(options);
+ }
+
+ private async initializeClient(options: BitbucketClientOptions): Promise {
+ try {
+ const config = await this.loadTokenConfig();
+ this.tokenSource = config.source;
+
+ this.client = axios.create({
+ baseURL: 'https://api.bitbucket.org/2.0',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ auth: {
+ username: config.email,
+ password: config.token
+ },
+ timeout: options.timeout || 30000,
+ maxRedirects: 5
+ });
+
+ // Request interceptor for token refresh and logging
+ this.client.interceptors.request.use(config => {
+ config.headers['User-Agent'] = `Bitbucket-MCP-Server/${this.getVersion()}`;
+ return config;
+ });
+
+ // Response interceptor for rate limiting and errors
+ this.client.interceptors.response.use(
+ response => response,
+ async (error: AxiosError) => {
+ await this.handleResponseError(error);
+ }
+ );
+
+ this.initialized = true;
+ } catch (error) {
+ console.error('Failed to load token configuration:', error);
+ throw new Error('Unable to initialize Bitbucket client - check token configuration');
+ }
+ }
+
+ private async ensureInitialized(): Promise {
+ if (!this.initialized) {
+ // Wait for initialization to complete
+ await new Promise(resolve => {
+ const checkInit = () => {
+ if (this.initialized) {
+ resolve(void 0);
+ } else {
+ setTimeout(checkInit, 10);
+ }
+ };
+ checkInit();
+ });
+ }
+ }
+
+ private async loadTokenConfig(): Promise {
+ return TokenConfigLoader.load();
+ }
+
+ private getVersion(): string {
+ try {
+ // Use synchronous fs for version lookup - no async needed
+ const fs = require('fs');
+ const path = require('path');
+ const pkgPath = path.join(process.cwd(), 'package.json');
+ if (fs.existsSync(pkgPath)) {
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
+ return pkg.version || '1.0.0';
+ }
+ return '1.0.0';
+ } catch {
+ return '1.0.0';
+ }
+ }
+
+ private async handleResponseError(error: AxiosError): Promise {
+ const status = error.response?.status;
+
+ if (status === 401) {
+ // Authentication error - provide detailed logging
+ const data = error.response?.data;
+ console.error('🔐 Bitbucket API Authentication Error (401):');
+ console.error(` Token source: ${this.tokenSource || 'unknown'}`);
+ console.error(` Request URL: ${error.config?.url}`);
+ console.error(` Request method: ${error.config?.method?.toUpperCase()}`);
+
+ if (typeof data === 'object' && data !== null) {
+ console.error(' Response data:', JSON.stringify(data, null, 2));
+ } else if (data) {
+ console.error(` Response data: ${data}`);
+ }
+
+ // Check if token might be expired or malformed
+ const authHeader = error.config?.headers?.Authorization;
+ if (typeof authHeader === 'string') {
+ const token = authHeader.replace('Bearer ', '');
+ const tokenLength = token.length;
+ const isJWT = token.includes('.');
+ console.error(` Token info: length=${tokenLength}, appears to be JWT=${isJWT}`);
+ }
+
+ console.error(' Possible causes:');
+ console.error(' - Token expired');
+ console.error(' - Token lacks required permissions (needs repository read access)');
+ console.error(' - Token is malformed or corrupted');
+ console.error(' - Repository/workspace access denied');
+ } else if (status === 403) {
+ // Forbidden - similar to auth but different permissions
+ console.error('🚫 Bitbucket API Forbidden Error (403):');
+ console.error(` Token source: ${this.tokenSource || 'unknown'}`);
+ console.error(` Request URL: ${error.config?.url}`);
+ console.error(' Possible causes:');
+ console.error(' - Token lacks permission to access this repository');
+ console.error(' - Repository is private and token has insufficient scope');
+ } else if (status === 429) {
+ // Rate limited - wait and retry
+ const retryAfter = error.response?.headers['retry-after'];
+ const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 5000;
+
+ console.log(`Rate limited. Retrying in ${delay}ms...`);
+ await this.sleep(delay);
+ } else if (status && status >= 400 && status < 500) {
+ // Other client error - log but don't retry
+ const data = error.response?.data;
+ let message: string;
+ if (typeof data === 'object' && data !== null && 'message' in data) {
+ message = String(data.message);
+ } else {
+ message = `Client error ${status}`;
+ }
+ console.error(`Bitbucket API ${status}: ${message}`);
+ } else if (status && status >= 500) {
+ // Server error - could implement exponential backoff retry
+ console.warn(`Server error ${status}, will retry...`);
+ }
+
+ throw error;
+ }
+
+ private sleep(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ /**
+ * Validate the authentication token by making a test API call.
+ */
+ async validateToken(): Promise<{ valid: boolean; message: string }> {
+ await this.ensureInitialized();
+
+ try {
+ // Make a simple API call to test authentication
+ const response = await this.client.get('/user');
+ return { valid: true, message: 'Token is valid' };
+ } catch (error: any) {
+ const status = error.response?.status;
+ if (status === 401) {
+ return { valid: false, message: 'Token is invalid or expired' };
+ } else if (status === 403) {
+ return { valid: false, message: 'Token lacks required permissions' };
+ } else {
+ return { valid: false, message: `Token validation failed: ${this.formatError(error)}` };
+ }
+ }
+ }
+
+ /**
+ * List pull requests in a repository.
+ */
+ async listPullRequests(
+ workspace: string,
+ repoSlug: string,
+ options?: {
+ state?: 'open' | 'closed' | 'all';
+ author?: string;
+ reviewer?: string;
+ since?: string;
+ }
+ ): Promise {
+ await this.ensureInitialized();
+
+ const params: Record = {};
+
+ if (options?.state) params.state = options.state;
+ if (options?.author) params.author = options.author;
+ if (options?.reviewer) params.reviewer = options.reviewer;
+ if (options?.since) params.since = options.since;
+
+ try {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests`,
+ { params }
+ );
+
+ return response.data.values || [];
+ } catch (error) {
+ throw new Error(`Failed to list pull requests: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get a specific pull request.
+ */
+ async getPullRequest(
+ workspace: string,
+ repoSlug: string,
+ prId: number
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`
+ );
+ return response.data;
+ } catch (error) {
+ throw new Error(`Failed to get pull request: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get pull request activities (events/actions on the PR).
+ */
+ async getPullRequestActivities(
+ workspace: string,
+ repoSlug: string,
+ prId: number,
+ options?: {
+ limit?: number;
+ start?: number;
+ }
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const params: Record = {};
+ if (options?.limit) params.limit = options.limit;
+ if (options?.start) params.start = options.start;
+
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/activity`,
+ { params }
+ );
+ return response.data;
+ } catch (error) {
+ throw new Error(`Failed to get pull request activities: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get pull request changes (files modified in the PR).
+ */
+ async getPullRequestChanges(
+ workspace: string,
+ repoSlug: string,
+ prId: number,
+ options?: {
+ limit?: number;
+ start?: number;
+ }
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const params: Record = {};
+ if (options?.limit) params.limit = options.limit;
+ if (options?.start) params.start = options.start;
+
+ const prResponse = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`
+ );
+ const sourceHash = prResponse.data.source?.commit?.hash;
+ const destHash = prResponse.data.destination?.commit?.hash;
+
+ if (sourceHash && destHash) {
+ const changesPath = `/repositories/${workspace}/${repoSlug}/diffstat/${sourceHash}..${destHash}`;
+ const response = await this.client.get(changesPath, { params });
+ return response.data;
+ }
+
+ throw new Error('Could not determine source and destination commits for PR');
+ } catch (error) {
+ throw new Error(`Failed to get pull request changes: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get pull request comments.
+ */
+ async getPullRequestComments(
+ workspace: string,
+ repoSlug: string,
+ prId: number,
+ options?: {
+ limit?: number;
+ start?: number;
+ }
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const params: Record = {};
+ if (options?.limit) params.limit = options.limit;
+ if (options?.start) params.start = options.start;
+
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/comments`,
+ { params }
+ );
+ return response.data;
+ } catch (error) {
+ throw new Error(`Failed to get pull request comments: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get a specific pull request comment.
+ */
+ async getPullRequestComment(
+ workspace: string,
+ repoSlug: string,
+ prId: number,
+ commentId: number
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/comments/${commentId}`
+ );
+ return response.data;
+ } catch (error) {
+ throw new Error(`Failed to get pull request comment: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get commits included in a pull request.
+ */
+ async getPullRequestCommits(
+ workspace: string,
+ repoSlug: string,
+ prId: number,
+ options?: {
+ limit?: number;
+ start?: number;
+ }
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const params: Record = {};
+ if (options?.limit) params.limit = options.limit;
+ if (options?.start) params.start = options.start;
+
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/commits`,
+ { params }
+ );
+ return response.data;
+ } catch (error) {
+ throw new Error(`Failed to get pull request commits: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get diff for a pull request.
+ */
+ async getPullRequestDiff(
+ workspace: string,
+ repoSlug: string,
+ prId: number,
+ options?: {
+ context?: number;
+ path?: string;
+ whitespace?: 'ignore-all' | 'ignore-changing' | 'ignore-eol' | 'show-all';
+ }
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const params: Record = {};
+ if (options?.context) params.context = options.context;
+ if (options?.whitespace) params.whitespace = options.whitespace;
+
+ if (options?.path) {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/diff/${options.path}`,
+ { params, responseType: 'text' }
+ );
+ return response.data;
+ }
+
+ const prResponse = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`
+ );
+ const sourceHash = prResponse.data.source?.commit?.hash;
+ const destHash = prResponse.data.destination?.commit?.hash;
+
+ if (sourceHash && destHash) {
+ const diffPath = `/repositories/${workspace}/${repoSlug}/diff/${sourceHash}..${destHash}`;
+ const response = await this.client.get(diffPath, { params, responseType: 'text' });
+ return response.data;
+ }
+
+ throw new Error('Could not determine source and destination commits for PR');
+ } catch (error) {
+ throw new Error(`Failed to get pull request diff: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get raw patch for a pull request.
+ */
+ async getPullRequestPatch(
+ workspace: string,
+ repoSlug: string,
+ prId: number
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const prResponse = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`
+ );
+ const sourceHash = prResponse.data.source?.commit?.hash;
+ const destHash = prResponse.data.destination?.commit?.hash;
+
+ if (sourceHash && destHash) {
+ const patchPath = `/repositories/${workspace}/${repoSlug}/diff/${sourceHash}..${destHash}`;
+ const response = await this.client.get(patchPath, { responseType: 'text' });
+ return { patch: response.data };
+ }
+
+ throw new Error('Could not determine source and destination commits for PR');
+ } catch (error) {
+ throw new Error(`Failed to get pull request patch: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get participants of a pull request.
+ */
+ async getPullRequestParticipants(
+ workspace: string,
+ repoSlug: string,
+ prId: number
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`
+ );
+ return response.data.participants || [];
+ } catch (error) {
+ throw new Error(`Failed to get pull request participants: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get reviewers of a pull request.
+ */
+ async getPullRequestReviewers(
+ workspace: string,
+ repoSlug: string,
+ prId: number
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`
+ );
+ return response.data.reviewers || [];
+ } catch (error) {
+ throw new Error(`Failed to get pull request reviewers: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get status of a pull request (merged, open, declined).
+ */
+ async getPullRequestStatus(
+ workspace: string,
+ repoSlug: string,
+ prId: number
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`
+ );
+ const pr = response.data;
+ return {
+ id: pr.id,
+ title: pr.title,
+ state: pr.state,
+ status: pr.status,
+ author: pr.author,
+ source_branch: pr.source?.branch?.name,
+ destination_branch: pr.destination?.branch?.name,
+ created_on: pr.created_on,
+ updated_on: pr.updated_on,
+ closed_on: pr.closed_on,
+ merge_commit: pr.merge_commit
+ };
+ } catch (error) {
+ throw new Error(`Failed to get pull request status: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get tasks associated with a pull request.
+ */
+ async getPullRequestTasks(
+ workspace: string,
+ repoSlug: string,
+ prId: number
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/tasks`
+ );
+ return response.data;
+ } catch (error) {
+ throw new Error(`Failed to get pull request tasks: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get task count for a pull request.
+ */
+ async getPullRequestTaskCount(
+ workspace: string,
+ repoSlug: string,
+ prId: number
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/tasks`
+ );
+ const tasks = response.data;
+ return { count: tasks.values?.length || 0 };
+ } catch (error) {
+ throw new Error(`Failed to get pull request task count: ${this.formatError(error)}`);
+ }
+ }
+
+ /**
+ * Get the full raw pull request with all details.
+ */
+ async getFullPullRequest(
+ workspace: string,
+ repoSlug: string,
+ prId: number
+ ): Promise {
+ await this.ensureInitialized();
+
+ try {
+ const response = await this.client.get(
+ `/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
+ {
+ params: {
+ fields: '+*'
+ }
+ }
+ );
+ return response.data;
+ } catch (error) {
+ throw new Error(`Failed to get full pull request: ${this.formatError(error)}`);
+ }
+ }
+
+ private formatError(error: any): string {
+ if (error?.response?.status) {
+ return `HTTP ${error.response.status}: ${error.message}`;
+ }
+ return error?.message || 'Unknown error';
+ }
+}
+
+export default BitbucketClient;
\ No newline at end of file
diff --git a/src/config.ts b/src/config.ts
new file mode 100644
index 0000000..9118ade
--- /dev/null
+++ b/src/config.ts
@@ -0,0 +1,175 @@
+import * as fs from 'fs';
+import * as path from 'path';
+
+/**
+ * Configuration with priority order:
+ * 1. Environment variables
+ * 2. .env file in project root
+ * 3. Interactive prompt (development fallback)
+ */
+
+export interface TokenConfig {
+ email: string;
+ token: string;
+ source: 'env' | 'dotenv' | 'prompt';
+}
+
+export interface DefaultConfig {
+ workspace: string | null;
+ repo: string | null;
+}
+
+/**
+ * Token configuration loader with priority order.
+ * Loads from environment variable, .env file, or prompts user.
+ */
+export class TokenConfigLoader {
+ private static readonly ENV_FILE = '.env';
+ private static readonly EMAIL_ENV = 'BITBUCKET_MCP_EMAIL';
+ private static readonly TOKEN_ENV = 'BITBUCKET_MCP_TOKEN';
+
+ /**
+ * Load configuration from configured sources with priority order.
+ * @returns TokenConfig with loaded email, token and source
+ */
+ public static async load(): Promise {
+ // Priority 1: Environment variables (highest precedence)
+ const envEmail = process.env[this.EMAIL_ENV];
+ const envToken = process.env[this.TOKEN_ENV];
+ if (envEmail && envToken && this.isValidToken(envToken)) {
+ return { email: envEmail, token: envToken, source: 'env' };
+ }
+
+ // Debug: Log environment check result
+ console.debug(`[DEBUG] Env var check - email present: ${!!envEmail}, token present: ${!!envToken}, valid: ${!!(envEmail && envToken && this.isValidToken(envToken))}`);
+
+ // Priority 2: .env file in project root
+ const { email: dotenvEmail, token: dotenvToken } = this.loadFromDotEnv();
+ console.debug(`[DEBUG] Dotenv config loaded - email: ${!!dotenvEmail}, token: ${!!dotenvToken}`);
+ if (dotenvEmail && dotenvToken && this.isValidToken(dotenvToken)) {
+ return { email: dotenvEmail, token: dotenvToken, source: 'dotenv' };
+ }
+
+ // Debug: Log dotenv check result
+ console.debug(`[DEBUG] Dotenv check - email present: ${!!dotenvEmail}, token present: ${!!dotenvToken}, valid: ${!!(dotenvEmail && dotenvToken && this.isValidToken(dotenvToken))}`);
+
+ // Priority 3: Interactive prompt (fallback)
+ console.warn(
+ '⚠️ No Bitbucket credentials found. Please set one of:'
+ );
+ console.warn(' 1. Export BITBUCKET_MCP_EMAIL= and BITBUCKET_MCP_TOKEN=');
+ console.warn(' 2. Add to .env: BITBUCKET_MCP_EMAIL= and BITBUCKET_MCP_TOKEN=');
+
+ return this.promptForCredentials().then(credentials => {
+ if (credentials.email && this.isValidToken(credentials.token)) {
+ return { ...credentials, source: 'prompt' };
+ }
+ throw new Error(
+ 'Invalid or missing Bitbucket credentials. Aborting.'
+ );
+ });
+ }
+
+ /**
+ * Load email and token from .env file in project root directory.
+ */
+ private static loadFromDotEnv(): { email: string | null; token: string | null } {
+ console.debug('[DEBUG] Attempting to load credentials from .env file');
+
+ try {
+ // Resolve to project root (look for package.json)
+ let currentDir = process.cwd();
+ while (currentDir !== '/') {
+ const pkgPath = path.join(currentDir, 'package.json');
+ if (fs.existsSync(pkgPath)) break;
+ currentDir = path.dirname(currentDir);
+ }
+
+ const envPath = path.join(currentDir, this.ENV_FILE);
+ if (!fs.existsSync(envPath)) return { email: null, token: null };
+
+ const envContent = fs.readFileSync(envPath, 'utf-8');
+
+ const emailMatch = envContent.match(
+ new RegExp(`${this.EMAIL_ENV}\\s*=\\s*([^\\r\\n]+)`, 'i')
+ );
+ const tokenMatch = envContent.match(
+ new RegExp(`${this.TOKEN_ENV}\\s*=\\s*([^\\r\\n]+)`, 'i')
+ );
+
+ return {
+ email: emailMatch ? emailMatch[1].trim() : null,
+ token: tokenMatch ? tokenMatch[1].trim() : null
+ };
+ } catch (error) {
+ console.error('Failed to load .env:', error);
+ return { email: null, token: null };
+ }
+ }
+
+ /**
+ * Validate Bitbucket access token format.
+ */
+ private static isValidToken(token: string): boolean {
+ if (!token || token.length < 8) return false;
+ const validPattern = /^[A-Za-z0-9=._\-+/]+$/;
+ return validPattern.test(token);
+ }
+
+ /**
+ * Prompt user for credentials (development fallback).
+ */
+ private static async promptForCredentials(): Promise<{ email: string; token: string }> {
+ // Skip prompting if running in non-Node.js environment
+ if (typeof require === 'undefined') {
+ return { email: '', token: '' };
+ }
+
+ const readline = await import('readline');
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+
+ const askQuestion = (question: string): Promise => {
+ return new Promise((resolve) => {
+ rl.question(question, (answer: string) => {
+ resolve(answer);
+ });
+ });
+ };
+
+ return new Promise((resolve) => {
+ rl.question(
+ 'Enter your Bitbucket email (or press Enter to skip): ',
+ async (email: string) => {
+ const token = await askQuestion('Enter your Bitbucket access token (or press Enter to skip): ');
+ rl.close();
+ resolve({ email: email || '', token: token || '' });
+ }
+ );
+ });
+ }
+}
+
+export class DefaultConfigLoader {
+ private static readonly WORKSPACE_ENV = 'DEFAULT_WORKSPACE';
+ private static readonly REPO_ENV = 'DEFAULT_REPO';
+
+ public static load(): DefaultConfig {
+ return {
+ workspace: process.env[this.WORKSPACE_ENV] || null,
+ repo: process.env[this.REPO_ENV] || null
+ };
+ }
+
+ public static getWorkspace(): string | null {
+ return process.env[this.WORKSPACE_ENV] || null;
+ }
+
+ public static getRepo(): string | null {
+ return process.env[this.REPO_ENV] || null;
+ }
+}
+
+export default TokenConfigLoader;
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..03678b2
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,347 @@
+#!/usr/bin/env node
+
+/**
+ * Bitbucket Pull Request MCP Server
+ *
+ * Configuration:
+ * 1. Export BITBUCKET_MCP_TOKEN= (environment variable)
+ * 2. Add to .env file in project root
+ * 3. Interactive prompt (development fallback)
+ */
+
+import { Server } from '@modelcontextprotocol/sdk/server/index.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import {
+ ListToolsRequestSchema,
+ CallToolRequestSchema,
+} from '@modelcontextprotocol/sdk/types.js';
+
+import { BitbucketRouter } from './router.js';
+
+const router = new BitbucketRouter();
+
+/**
+ * MCP Server implementation for Bitbucket Pull Requests.
+ */
+class BitbucketMCPServer {
+ private server: Server;
+
+ constructor() {
+ this.server = new Server({
+ name: 'bitbucket-pullrequests',
+ version: '1.0.0',
+ }, {
+ capabilities: {
+ tools: {},
+ },
+ });
+
+ // Handle requests
+ this.setupRequestHandlers();
+ }
+
+
+ /**
+ * Setup MCP request handlers.
+ */
+ private setupRequestHandlers(): void {
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
+ return {
+ tools: [
+ {
+ name: 'validate_token',
+ description: 'Validate the Bitbucket authentication token',
+ inputSchema: {
+ type: 'object',
+ properties: {},
+ },
+ },
+ {
+ name: 'list_pull_requests',
+ description: 'List pull requests in a Bitbucket repository',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ state: { type: 'string', enum: ['open', 'closed', 'all'] },
+ author: { type: 'string' },
+ },
+ },
+ },
+ {
+ name: 'get_pull_request',
+ description: 'Get details of a specific pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ },
+ },
+ },
+ {
+ name: 'get_pull_request_activities',
+ description: 'Get activities/events for a pull request (opens, closes, comments, reviews, etc.)',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ limit: { type: 'integer', description: 'Max results per page (default 25)' },
+ start: { type: 'integer', description: 'Pagination start index' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_changes',
+ description: 'Get files changed in a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ limit: { type: 'integer', description: 'Max results per page (default 25)' },
+ start: { type: 'integer', description: 'Pagination start index' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_comments',
+ description: 'Get all comments on a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ limit: { type: 'integer', description: 'Max results per page (default 25)' },
+ start: { type: 'integer', description: 'Pagination start index' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_comment',
+ description: 'Get a specific comment on a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ commentId: { type: 'integer', description: 'The comment ID' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId', 'commentId'],
+ },
+ },
+ {
+ name: 'get_pull_request_commits',
+ description: 'Get commits included in a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ limit: { type: 'integer', description: 'Max results per page (default 25)' },
+ start: { type: 'integer', description: 'Pagination start index' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_diff',
+ description: 'Get the diff of a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ path: { type: 'string', description: 'Specific file path to get diff for' },
+ context: { type: 'integer', description: 'Number of context lines around changes' },
+ whitespace: { type: 'string', enum: ['ignore-all', 'ignore-changing', 'ignore-eol', 'show-all'] },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_patch',
+ description: 'Get the raw patch file for a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_participants',
+ description: 'Get participants of a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_reviewers',
+ description: 'Get reviewers of a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_status',
+ description: 'Get status/state of a pull request (open, merged, declined)',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_tasks',
+ description: 'Get tasks associated with a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_pull_request_task_count',
+ description: 'Get count of tasks on a pull request',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ {
+ name: 'get_full_pull_request',
+ description: 'Get full detailed information about a pull request with all fields expanded',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ workspace: { type: 'string' },
+ repository: { type: 'string' },
+ pullRequestId: { type: 'integer' },
+ },
+ required: ['workspace', 'repository', 'pullRequestId'],
+ },
+ },
+ ],
+ };
+ });
+
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const { name, arguments: params } = request.params;
+
+ try {
+ console.log(`Executing tool: ${name}`);
+ console.log('Parameters:', JSON.stringify(params));
+
+ const result = await router.executeTool(name, params as any);
+
+ if (!result.success) {
+ throw new Error(result.error || 'Tool execution failed');
+ }
+
+ return {
+ content: [
+ {
+ type: 'text',
+ text: JSON.stringify(result.data, null, 2),
+ },
+ ],
+ };
+ } catch (error) {
+ console.error(`Tool error (${name}):`, error);
+
+ throw {
+ name: 'ToolExecutionError',
+ message: typeof error === 'string' ? error : (error as Error).message || 'Unknown error',
+ };
+ }
+ });
+ }
+
+ /**
+ * Start the MCP server.
+ */
+ async start(): Promise {
+ try {
+ const transport = new StdioServerTransport();
+ await this.server.connect(transport);
+
+ console.log('✅ Bitbucket MCP Server started');
+ console.log(' Name: bitbucket-pullrequests v1.0.0');
+
+ // Log token source for debugging
+ if (process.env.BITBUCKET_MCP_TOKEN) {
+ console.log(' Token source: environment variable');
+ } else {
+ console.log(' Token source: .env file or interactive prompt');
+ }
+
+ // Validate token on startup
+ try {
+ console.log('🔍 Validating Bitbucket token...');
+ const router = new BitbucketRouter();
+ const validation = await router.executeTool('validate_token', {});
+ if (validation.success) {
+ console.log('✅ Token validation successful');
+ } else {
+ console.error('❌ Token validation failed:', validation.error);
+ console.error(' Please check your BITBUCKET_MCP_TOKEN configuration');
+ console.error(' Required permissions: Repository read access');
+ }
+ } catch (error) {
+ console.error('❌ Token validation error:', error);
+ }
+
+ } catch (error) {
+ console.error('❌ Server startup failed:', error);
+ process.exit(1);
+ }
+ }
+}
+
+// Entry point
+const server = new BitbucketMCPServer();
+server.start().catch((error) => {
+ console.error('Fatal error:', error);
+ process.exit(1);
+});
\ No newline at end of file
diff --git a/src/router.ts b/src/router.ts
new file mode 100644
index 0000000..eec5af4
--- /dev/null
+++ b/src/router.ts
@@ -0,0 +1,489 @@
+/**
+ * MCP tool router that maps tool calls to Bitbucket API operations.
+ */
+
+import { BitbucketClient } from './bitbucket-client.js';
+import { DefaultConfigLoader } from './config.js';
+
+interface ToolCallParams {
+ [key: string]: any;
+}
+
+export interface ToolResult {
+ success: boolean;
+ data?: any;
+ error?: string;
+}
+
+/**
+ * Router that maps MCP tool calls to Bitbucket API operations.
+ */
+export class BitbucketRouter {
+ private client: BitbucketClient;
+
+ constructor() {
+ this.client = new BitbucketClient();
+ }
+
+ private getDefaultParams(params: ToolCallParams): { workspace: string | undefined; repoSlug: string | undefined } {
+ const defaults = DefaultConfigLoader.load();
+ return {
+ workspace: params.workspace || defaults.workspace || undefined,
+ repoSlug: params.repository || params.repo || defaults.repo || undefined
+ };
+ }
+
+ /**
+ * Validate the Bitbucket token.
+ */
+ private async validateToken(): Promise {
+ try {
+ const result = await this.client.validateToken();
+ return {
+ success: result.valid,
+ data: result
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Token validation failed: ${String(error)}`
+ };
+ }
+ }
+
+ /**
+ * Execute a tool call and return MCP-compatible result.
+ */
+ async executeTool(
+ toolName: string,
+ params: ToolCallParams
+ ): Promise {
+ switch (toolName) {
+ case 'validate_token':
+ return this.validateToken();
+
+ case 'list_pull_requests':
+ return this.listPullRequests(params);
+
+ case 'get_pull_request':
+ return this.getPullRequest(params);
+
+ case 'get_pull_request_activities':
+ return this.getPullRequestActivities(params);
+
+ case 'get_pull_request_changes':
+ return this.getPullRequestChanges(params);
+
+ case 'get_pull_request_comments':
+ return this.getPullRequestComments(params);
+
+ case 'get_pull_request_comment':
+ return this.getPullRequestComment(params);
+
+ case 'get_pull_request_commits':
+ return this.getPullRequestCommits(params);
+
+ case 'get_pull_request_diff':
+ return this.getPullRequestDiff(params);
+
+ case 'get_pull_request_patch':
+ return this.getPullRequestPatch(params);
+
+ case 'get_pull_request_participants':
+ return this.getPullRequestParticipants(params);
+
+ case 'get_pull_request_reviewers':
+ return this.getPullRequestReviewers(params);
+
+ case 'get_pull_request_status':
+ return this.getPullRequestStatus(params);
+
+ case 'get_pull_request_tasks':
+ return this.getPullRequestTasks(params);
+
+ case 'get_pull_request_task_count':
+ return this.getPullRequestTaskCount(params);
+
+ case 'get_full_pull_request':
+ return this.getFullPullRequest(params);
+
+ default:
+ return {
+ success: false,
+ error: `Unknown tool: ${toolName}`
+ };
+ }
+ }
+
+ /**
+ * List pull requests in a repository.
+ */
+ private async listPullRequests(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+
+ if (!workspace || !repoSlug) {
+ return {
+ success: false,
+ error: 'Missing required parameters: workspace and repository'
+ };
+ }
+
+ // Default to open PRs if state not specified
+ const options = params.state ? { state: params.state } : undefined;
+
+ const prs = await this.client.listPullRequests(workspace, repoSlug, options);
+
+ return {
+ success: true,
+ data: {
+ pull_requests: prs,
+ count: prs.length
+ }
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `List pull requests failed: ${typeof error === 'string' ? error : String(error)}`
+ };
+ }
+ }
+
+ /**
+ * Get a specific pull request.
+ */
+ private async getPullRequest(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return {
+ success: false,
+ error: 'Missing required parameters'
+ };
+ }
+
+ const pr = await this.client.getPullRequest(workspace, repoSlug, prId);
+
+ return {
+ success: true,
+ data: pr
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Get pull request failed: ${typeof error === 'string' ? error : String(error)}`
+ };
+ }
+ }
+
+ /**
+ * Get pull request activities (events/actions).
+ */
+ private async getPullRequestActivities(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestActivities(workspace, repoSlug, prId, {
+ limit: params.limit,
+ start: params.start
+ });
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get activities failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get pull request changes (files modified).
+ */
+ private async getPullRequestChanges(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestChanges(workspace, repoSlug, prId, {
+ limit: params.limit,
+ start: params.start
+ });
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get changes failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get pull request comments.
+ */
+ private async getPullRequestComments(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestComments(workspace, repoSlug, prId, {
+ limit: params.limit,
+ start: params.start
+ });
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get comments failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get a specific pull request comment.
+ */
+ private async getPullRequestComment(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+ const commentId = parseInt(params.commentId || params.comment_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId) || isNaN(commentId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestComment(workspace, repoSlug, prId, commentId);
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get comment failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get commits in a pull request.
+ */
+ private async getPullRequestCommits(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestCommits(workspace, repoSlug, prId, {
+ limit: params.limit,
+ start: params.start
+ });
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get commits failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get pull request diff.
+ */
+ private async getPullRequestDiff(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestDiff(workspace, repoSlug, prId, {
+ context: params.context,
+ path: params.path,
+ whitespace: params.whitespace
+ });
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get diff failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get pull request patch.
+ */
+ private async getPullRequestPatch(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestPatch(workspace, repoSlug, prId);
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get patch failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get pull request participants.
+ */
+ private async getPullRequestParticipants(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestParticipants(workspace, repoSlug, prId);
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get participants failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get pull request reviewers.
+ */
+ private async getPullRequestReviewers(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestReviewers(workspace, repoSlug, prId);
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get reviewers failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get pull request status.
+ */
+ private async getPullRequestStatus(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestStatus(workspace, repoSlug, prId);
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get status failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get pull request tasks.
+ */
+ private async getPullRequestTasks(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestTasks(workspace, repoSlug, prId);
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get tasks failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get pull request task count.
+ */
+ private async getPullRequestTaskCount(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getPullRequestTaskCount(workspace, repoSlug, prId);
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get task count failed: ${String(error)}` };
+ }
+ }
+
+ /**
+ * Get full pull request details.
+ */
+ private async getFullPullRequest(
+ params: ToolCallParams
+ ): Promise {
+ try {
+ const { workspace, repoSlug } = this.getDefaultParams(params);
+ const prId = parseInt(params.pullRequestId || params.pr_id, 10);
+
+ if (!workspace || !repoSlug || isNaN(prId)) {
+ return { success: false, error: 'Missing required parameters' };
+ }
+
+ const result = await this.client.getFullPullRequest(workspace, repoSlug, prId);
+
+ return { success: true, data: result };
+ } catch (error) {
+ return { success: false, error: `Get full PR failed: ${String(error)}` };
+ }
+ }
+}
+
+export default BitbucketRouter;
\ No newline at end of file
diff --git a/test-client.js b/test-client.js
new file mode 100644
index 0000000..cb4f8d2
--- /dev/null
+++ b/test-client.js
@@ -0,0 +1,87 @@
+#!/usr/bin/env node
+
+/**
+ * Simple test client for Bitbucket MCP Server.
+ * Tests the router and API client functionality.
+ */
+
+import { BitbucketRouter } from './dist/router.js';
+
+const router = new BitbucketRouter();
+
+console.log('🧪 Testing Bitbucket MCP Router\n');
+
+// Test 1: List pull requests (will fail without valid token, but tests structure)
+console.log('Test 1: list_pull_requests');
+try {
+ const result = await router.executeTool('list_pull_requests', {
+ workspace: 'test-workspace',
+ repository: 'test-repo'
+ });
+ console.log('Result:', JSON.stringify(result, null, 2));
+} catch (error) {
+ console.log('Expected error (no valid token):', error.message);
+}
+
+// Test 2: Get pull request
+console.log('\nTest 2: get_pull_request');
+try {
+ const result = await router.executeTool('get_pull_request', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 12345
+ });
+ console.log('Result:', JSON.stringify(result, null, 2));
+} catch (error) {
+ console.log('Expected error (no valid token):', error.message);
+}
+
+// Test 3: Get PR status
+console.log('\nTest 3: get_pull_request_status');
+try {
+ const result = await router.executeTool('get_pull_request_status', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 12345
+ });
+ console.log('Result:', JSON.stringify(result, null, 2));
+} catch (error) {
+ console.log('Expected error (no valid token):', error.message);
+}
+
+// Test 4: Get PR comments
+console.log('\nTest 4: get_pull_request_comments');
+try {
+ const result = await router.executeTool('get_pull_request_comments', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 12345
+ });
+ console.log('Result:', JSON.stringify(result, null, 2));
+} catch (error) {
+ console.log('Expected error (no valid token):', error.message);
+}
+
+// Test 6: Invalid tool name
+console.log('\nTest 5: Unknown tool');
+try {
+ const result = await router.executeTool('invalid_tool', {});
+ console.log('Result:', JSON.stringify(result, null, 2));
+} catch (error) {
+ console.log('Error:', error.message);
+}
+
+// Test 5: Get PR activities
+console.log('\nTest 5: get_pull_request_activities');
+try {
+const result = await router.executeTool('get_pull_request_activities', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 12345
+});
+console.log('Result:', JSON.stringify(result, null, 2));
+} catch (error) {
+console.log('Expected error (no valid token):', error.message);
+}
+
+console.log('\n✅ All tests completed!');
\ No newline at end of file
diff --git a/tests/integration/bitbucket-api.test.ts b/tests/integration/bitbucket-api.test.ts
new file mode 100644
index 0000000..611cb5b
--- /dev/null
+++ b/tests/integration/bitbucket-api.test.ts
@@ -0,0 +1,294 @@
+/**
+ * Integration tests for Bitbucket MCP Server
+ * Tests against real Bitbucket API with iland-software-engineering/1111-frontend repo
+ *
+ * Requires BITBUCKET_MCP_EMAIL and BITBUCKET_MCP_TOKEN in .env or environment variable.
+ */
+
+import { describe, it, expect, beforeAll } from 'vitest';
+import { BitbucketClient } from '../../src/bitbucket-client.js';
+import { BitbucketRouter, ToolResult } from '../../src/router.js';
+import * as fs from 'fs';
+import * as path from 'path';
+import * as dotenv from 'dotenv';
+
+const WORKSPACE = 'iland-software-engineering';
+const REPO_SLUG = '1111-front';
+
+dotenv.config({ path: path.resolve(process.cwd(), '.env') });
+
+const EMAIL = process.env.BITBUCKET_MCP_EMAIL || '';
+const TOKEN = process.env.BITBUCKET_MCP_TOKEN || '';
+const HAS_TOKEN = !!EMAIL && !!TOKEN && TOKEN.length >= 20;
+
+describe('Bitbucket API Integration Tests', () => {
+ let client: BitbucketClient;
+ let router: BitbucketRouter;
+
+ beforeAll(() => {
+ client = new BitbucketClient();
+ router = new BitbucketRouter();
+ }, 35000);
+
+ describe('Pull Request Listing', () => {
+ it('should list pull requests', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ expect(Array.isArray(prs)).toBe(true);
+ if (prs.length > 0) {
+ expect(prs[0]).toHaveProperty('id');
+ expect(prs[0]).toHaveProperty('title');
+ expect(prs[0]).toHaveProperty('state');
+ }
+ });
+
+ it('should list open pull requests', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG, { state: 'open' });
+ expect(Array.isArray(prs)).toBe(true);
+ });
+
+ it('should list closed pull requests', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG, { state: 'closed' });
+ expect(Array.isArray(prs)).toBe(true);
+ });
+ });
+
+ describe('Pull Request Details', () => {
+ it('should get a specific pull request', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG, { state: 'open' });
+
+ if (prs.length === 0) {
+ const closedPrs = await client.listPullRequests(WORKSPACE, REPO_SLUG, { state: 'closed' });
+ if (closedPrs.length === 0) {
+ console.log('⚠️ No PRs found in repository, skipping specific PR test');
+ return;
+ }
+ const pr = await client.getPullRequest(WORKSPACE, REPO_SLUG, closedPrs[0].id);
+ expect(pr).toHaveProperty('id');
+ expect(pr).toHaveProperty('title');
+ return;
+ }
+
+ const pr = await client.getPullRequest(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(pr).toHaveProperty('id');
+ expect(pr).toHaveProperty('title');
+ expect(pr.id).toBe(prs[0].id);
+ });
+
+ it('should get pull request status', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping status test');
+ return;
+ }
+
+ const status = await client.getPullRequestStatus(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(status).toHaveProperty('id');
+ expect(status).toHaveProperty('state');
+ expect(status).toHaveProperty('title');
+ });
+
+ it('should get pull request reviewers', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping reviewers test');
+ return;
+ }
+
+ const reviewers = await client.getPullRequestReviewers(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(Array.isArray(reviewers)).toBe(true);
+ });
+
+ it('should get pull request participants', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping participants test');
+ return;
+ }
+
+ const participants = await client.getPullRequestParticipants(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(participants).toBeDefined();
+ });
+ });
+
+ describe('Pull Request Activities & Comments', () => {
+ it('should get pull request activities', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping activities test');
+ return;
+ }
+
+ const activities = await client.getPullRequestActivities(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(activities).toBeDefined();
+ expect(activities).toHaveProperty('values');
+ });
+
+ it('should get pull request comments', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping comments test');
+ return;
+ }
+
+ const comments = await client.getPullRequestComments(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(comments).toBeDefined();
+ expect(comments).toHaveProperty('values');
+ });
+
+ it('should get pull request commits', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping commits test');
+ return;
+ }
+
+ const commits = await client.getPullRequestCommits(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(commits).toBeDefined();
+ expect(commits).toHaveProperty('values');
+ });
+ });
+
+ describe('Pull Request Changes & Diff', () => {
+ it('should get pull request changes', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping changes test');
+ return;
+ }
+
+ const changes = await client.getPullRequestChanges(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(changes).toBeDefined();
+ expect(changes).toHaveProperty('values');
+ });
+
+ it('should get pull request diff', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping diff test');
+ return;
+ }
+
+ const diff = await client.getPullRequestDiff(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(diff).toBeDefined();
+ });
+
+ it('should get pull request patch', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping patch test');
+ return;
+ }
+
+ const patch = await client.getPullRequestPatch(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(patch).toHaveProperty('patch');
+ expect(typeof patch.patch).toBe('string');
+ });
+ });
+
+ describe('Pull Request Tasks', () => {
+ it('should get pull request tasks', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping tasks test');
+ return;
+ }
+
+ const tasks = await client.getPullRequestTasks(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(tasks).toBeDefined();
+ });
+
+ it('should get pull request task count', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping task count test');
+ return;
+ }
+
+ const taskCount = await client.getPullRequestTaskCount(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(taskCount).toBeDefined();
+ });
+ });
+
+ describe('Full Pull Request', () => {
+ it('should get full pull request details', async () => {
+ if (!HAS_TOKEN) return;
+ const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG);
+ if (prs.length === 0) {
+ console.log('⚠️ No PRs found, skipping full PR test');
+ return;
+ }
+
+ const fullPR = await client.getFullPullRequest(WORKSPACE, REPO_SLUG, prs[0].id);
+ expect(fullPR).toHaveProperty('id');
+ expect(fullPR).toHaveProperty('title');
+ expect(fullPR).toHaveProperty('source');
+ expect(fullPR).toHaveProperty('destination');
+ });
+ });
+});
+
+describe('Bitbucket Router Integration', () => {
+ let router: BitbucketRouter;
+
+ beforeAll(() => {
+ delete process.env.DEFAULT_WORKSPACE;
+ delete process.env.DEFAULT_REPO;
+ router = new BitbucketRouter();
+ });
+
+ beforeEach(() => {
+ delete process.env.DEFAULT_WORKSPACE;
+ delete process.env.DEFAULT_REPO;
+ });
+
+ describe('Router parameter validation', () => {
+ it('should require workspace for list_pull_requests', async () => {
+ const result = await router.executeTool('list_pull_requests', {
+ repository: REPO_SLUG
+ }) as ToolResult;
+
+ expect(result.success).toBe(false);
+ });
+
+ it('should require workspace and pullRequestId for get_pull_request', async () => {
+ const result = await router.executeTool('get_pull_request', {
+ workspace: WORKSPACE
+ }) as ToolResult;
+
+ expect(result.success).toBe(false);
+ });
+
+ it('should handle unknown tool', async () => {
+ const result = await router.executeTool('unknown_tool', {}) as ToolResult;
+
+ expect(result.success).toBe(false);
+ expect(result.error).toContain('Unknown tool');
+ });
+ });
+});
+
+describe('Error Handling', () => {
+ it('should handle invalid pull request ID', async () => {
+ if (!HAS_TOKEN) return;
+ try {
+ await new BitbucketClient().getPullRequest(WORKSPACE, REPO_SLUG, 999999999);
+ } catch (error) {
+ expect(error).toBeDefined();
+ }
+ });
+});
diff --git a/tests/unit/config.test.ts b/tests/unit/config.test.ts
new file mode 100644
index 0000000..5515a08
--- /dev/null
+++ b/tests/unit/config.test.ts
@@ -0,0 +1,141 @@
+/**
+ * Unit tests for TokenConfigLoader
+ */
+
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import { TokenConfigLoader, TokenConfig, DefaultConfigLoader, DefaultConfig } from '../../src/config.js';
+
+describe('TokenConfigLoader', () => {
+ const originalEnv = { ...process.env };
+
+ beforeEach(() => {
+ process.env = { ...originalEnv };
+ delete process.env.BITBUCKET_MCP_TOKEN;
+ delete process.env.DEFAULT_WORKSPACE;
+ delete process.env.DEFAULT_REPO;
+ });
+
+ afterEach(() => {
+ process.env = originalEnv;
+ vi.restoreAllMocks();
+ });
+
+ describe('token validation', () => {
+ it('should accept valid tokens (alphanumeric with special chars)', () => {
+ const validPattern = /^[A-Za-z0-9=._-]+$/;
+
+ expect(validPattern.test('abc123def45678901234567')).toBe(true);
+ expect(validPattern.test('ATBB1234567890abcdefgh')).toBe(true);
+ expect(validPattern.test('Bearer_token.with=chars12')).toBe(true);
+ expect(validPattern.test('a'.repeat(20))).toBe(true);
+ });
+
+ it('should reject tokens that are too short', () => {
+ const shortToken = 'abc123';
+ expect(shortToken.length).toBeLessThan(20);
+ });
+
+ it('should reject tokens with invalid characters', () => {
+ const invalidToken = 'token with spaces!@#$%';
+ const validPattern = /^[A-Za-z0-9=._-]+$/;
+ expect(validPattern.test(invalidToken)).toBe(false);
+ });
+ });
+
+ describe('environment variable loading', () => {
+ it('should load token from BITBUCKET_MCP_TOKEN env var', async () => {
+ process.env.BITBUCKET_MCP_EMAIL = 'test@example.com';
+ process.env.BITBUCKET_MCP_TOKEN = 'valid_test_token_12345678901234567';
+
+ const config = await TokenConfigLoader.load();
+
+ expect(config.token).toBe('valid_test_token_12345678901234567');
+ expect(config.source).toBe('env');
+ });
+ });
+
+ describe('TokenConfig interface', () => {
+ it('should return correct config structure for env source', async () => {
+ process.env.BITBUCKET_MCP_TOKEN = 'test_token_12345678901234567';
+
+ const config = await TokenConfigLoader.load();
+
+ expect(config).toHaveProperty('token');
+ expect(config).toHaveProperty('source');
+ expect(['env', 'dotenv', 'prompt']).toContain(config.source);
+ });
+ });
+});
+
+describe('DefaultConfigLoader', () => {
+ const originalEnv = { ...process.env };
+
+ beforeEach(() => {
+ process.env = { ...originalEnv };
+ delete process.env.DEFAULT_WORKSPACE;
+ delete process.env.DEFAULT_REPO;
+ });
+
+ afterEach(() => {
+ process.env = originalEnv;
+ });
+
+ describe('load', () => {
+ it('should return null values when env vars are not set', () => {
+ const config = DefaultConfigLoader.load();
+
+ expect(config.workspace).toBeNull();
+ expect(config.repo).toBeNull();
+ });
+
+ it('should return workspace and repo from env vars when set', () => {
+ process.env.DEFAULT_WORKSPACE = 'my-workspace';
+ process.env.DEFAULT_REPO = 'my-repo';
+
+ const config = DefaultConfigLoader.load();
+
+ expect(config.workspace).toBe('my-workspace');
+ expect(config.repo).toBe('my-repo');
+ });
+
+ it('should return partial config when only workspace is set', () => {
+ process.env.DEFAULT_WORKSPACE = 'only-workspace';
+
+ const config = DefaultConfigLoader.load();
+
+ expect(config.workspace).toBe('only-workspace');
+ expect(config.repo).toBeNull();
+ });
+
+ it('should return partial config when only repo is set', () => {
+ process.env.DEFAULT_REPO = 'only-repo';
+
+ const config = DefaultConfigLoader.load();
+
+ expect(config.workspace).toBeNull();
+ expect(config.repo).toBe('only-repo');
+ });
+ });
+
+ describe('getWorkspace', () => {
+ it('should return null when DEFAULT_WORKSPACE is not set', () => {
+ expect(DefaultConfigLoader.getWorkspace()).toBeNull();
+ });
+
+ it('should return workspace value when set', () => {
+ process.env.DEFAULT_WORKSPACE = 'test-workspace';
+ expect(DefaultConfigLoader.getWorkspace()).toBe('test-workspace');
+ });
+ });
+
+ describe('getRepo', () => {
+ it('should return null when DEFAULT_REPO is not set', () => {
+ expect(DefaultConfigLoader.getRepo()).toBeNull();
+ });
+
+ it('should return repo value when set', () => {
+ process.env.DEFAULT_REPO = 'test-repo';
+ expect(DefaultConfigLoader.getRepo()).toBe('test-repo');
+ });
+ });
+});
diff --git a/tests/unit/router.test.ts b/tests/unit/router.test.ts
new file mode 100644
index 0000000..922b5a5
--- /dev/null
+++ b/tests/unit/router.test.ts
@@ -0,0 +1,444 @@
+/**
+ * Unit tests for BitbucketRouter
+ */
+
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { BitbucketRouter, ToolResult } from '../../src/router.js';
+
+// Mock the BitbucketClient
+vi.mock('../../src/bitbucket-client.js', () => {
+ return {
+ BitbucketClient: vi.fn().mockImplementation(() => ({
+ listPullRequests: vi.fn().mockResolvedValue([
+ { id: 1, title: 'Test PR 1', state: 'OPEN' },
+ { id: 2, title: 'Test PR 2', state: 'MERGED' }
+ ]),
+ getPullRequest: vi.fn().mockResolvedValue({
+ id: 1,
+ title: 'Test PR',
+ state: 'OPEN',
+ source: { branch: { name: 'feature' } },
+ destination: { branch: { name: 'main' } }
+ }),
+ getPullRequestStatus: vi.fn().mockResolvedValue({
+ id: 1,
+ title: 'Test PR',
+ state: 'OPEN',
+ status: 'NORMAL'
+ }),
+ getPullRequestActivities: vi.fn().mockResolvedValue({
+ values: [{ action: 'OPEN' }]
+ }),
+ getPullRequestChanges: vi.fn().mockResolvedValue({
+ values: [{ type: 'modified', path: 'src/test.ts' }]
+ }),
+ getPullRequestComments: vi.fn().mockResolvedValue({
+ values: [{ id: 1, content: { raw: 'Test comment' } }]
+ }),
+ getPullRequestComment: vi.fn().mockResolvedValue({
+ id: 1,
+ content: { raw: 'Test comment' }
+ }),
+ getPullRequestCommits: vi.fn().mockResolvedValue({
+ values: [{ hash: 'abc123' }]
+ }),
+ getPullRequestDiff: vi.fn().mockResolvedValue({
+ diff: '--- test.ts\n+++ test.ts\n@@ -1 +1 @@'
+ }),
+ getPullRequestPatch: vi.fn().mockResolvedValue({
+ patch: '--- original\n+++ modified'
+ }),
+ getPullRequestParticipants: vi.fn().mockResolvedValue({
+ values: [{ user: { display_name: 'Test User' } }]
+ }),
+ getPullRequestReviewers: vi.fn().mockResolvedValue([
+ { user: { display_name: 'Reviewer 1' } }
+ ]),
+ getPullRequestTasks: vi.fn().mockResolvedValue({
+ values: []
+ }),
+ getPullRequestTaskCount: vi.fn().mockResolvedValue({ count: 0 }),
+ getFullPullRequest: vi.fn().mockResolvedValue({
+ id: 1,
+ title: 'Test PR',
+ description: 'Test description'
+ })
+ }))
+ };
+});
+
+describe('BitbucketRouter', () => {
+ let router: BitbucketRouter;
+ const originalEnv = { ...process.env };
+
+ beforeEach(() => {
+ process.env = { ...originalEnv };
+ delete process.env.DEFAULT_WORKSPACE;
+ delete process.env.DEFAULT_REPO;
+ router = new BitbucketRouter();
+ });
+
+ afterEach(() => {
+ process.env = originalEnv;
+ });
+
+ describe('list_pull_requests', () => {
+ it('should return pull requests for valid parameters', async () => {
+ const result = await router.executeTool('list_pull_requests', {
+ workspace: 'test-workspace',
+ repository: 'test-repo'
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('pull_requests');
+ expect(result.data.pull_requests).toHaveLength(2);
+ });
+
+ it('should fail without workspace', async () => {
+ const result = await router.executeTool('list_pull_requests', {
+ repository: 'test-repo'
+ }) as ToolResult;
+
+ expect(result.success).toBe(false);
+ expect(result.error).toContain('Missing required parameters');
+ });
+
+ it('should fail without repository', async () => {
+ const result = await router.executeTool('list_pull_requests', {
+ workspace: 'test-workspace'
+ }) as ToolResult;
+
+ expect(result.success).toBe(false);
+ expect(result.error).toContain('Missing required parameters');
+ });
+
+ it('should accept "repo" as alias for "repository"', async () => {
+ const result = await router.executeTool('list_pull_requests', {
+ workspace: 'test-workspace',
+ repo: 'test-repo'
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+
+ it('should use DEFAULT_WORKSPACE when workspace is not provided', async () => {
+ process.env.DEFAULT_WORKSPACE = 'default-workspace';
+ process.env.DEFAULT_REPO = 'default-repo';
+
+ const result = await router.executeTool('list_pull_requests', {}) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+
+ it('should use DEFAULT_REPO when repository is not provided', async () => {
+ process.env.DEFAULT_REPO = 'default-repo';
+
+ const result = await router.executeTool('list_pull_requests', {
+ workspace: 'test-workspace'
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+
+ it('should use both DEFAULT_WORKSPACE and DEFAULT_REPO when neither is provided', async () => {
+ process.env.DEFAULT_WORKSPACE = 'default-workspace';
+ process.env.DEFAULT_REPO = 'default-repo';
+
+ const result = await router.executeTool('list_pull_requests', {}) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+
+ it('should prefer explicit params over defaults', async () => {
+ process.env.DEFAULT_WORKSPACE = 'default-workspace';
+ process.env.DEFAULT_REPO = 'default-repo';
+
+ const result = await router.executeTool('list_pull_requests', {
+ workspace: 'explicit-workspace',
+ repository: 'explicit-repo'
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+
+ it('should still fail when no workspace and no default available', async () => {
+ const result = await router.executeTool('list_pull_requests', {
+ repository: 'test-repo'
+ }) as ToolResult;
+
+ expect(result.success).toBe(false);
+ expect(result.error).toContain('Missing required parameters');
+ });
+
+ it('should still fail when no repository and no default available', async () => {
+ const result = await router.executeTool('list_pull_requests', {
+ workspace: 'test-workspace'
+ }) as ToolResult;
+
+ expect(result.success).toBe(false);
+ expect(result.error).toContain('Missing required parameters');
+ });
+ });
+
+ describe('get_pull_request', () => {
+ it('should return pull request for valid parameters', async () => {
+ const result = await router.executeTool('get_pull_request', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('id', 1);
+ });
+
+ it('should accept pullRequestId as pr_id', async () => {
+ const result = await router.executeTool('get_pull_request', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pr_id: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+
+ it('should fail with invalid pullRequestId', async () => {
+ const result = await router.executeTool('get_pull_request', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 'invalid'
+ }) as ToolResult;
+
+ expect(result.success).toBe(false);
+ });
+ });
+
+ describe('get_pull_request_activities', () => {
+ it('should return activities for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_activities', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('values');
+ });
+
+ it('should accept pagination parameters', async () => {
+ const result = await router.executeTool('get_pull_request_activities', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1,
+ limit: 10,
+ start: 0
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+ });
+
+ describe('get_pull_request_changes', () => {
+ it('should return changes for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_changes', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('values');
+ });
+ });
+
+ describe('get_pull_request_comments', () => {
+ it('should return comments for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_comments', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('values');
+ });
+ });
+
+ describe('get_pull_request_comment', () => {
+ it('should return specific comment', async () => {
+ const result = await router.executeTool('get_pull_request_comment', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1,
+ commentId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('id', 1);
+ });
+
+ it('should fail without commentId', async () => {
+ const result = await router.executeTool('get_pull_request_comment', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(false);
+ });
+ });
+
+ describe('get_pull_request_commits', () => {
+ it('should return commits for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_commits', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('values');
+ });
+ });
+
+ describe('get_pull_request_diff', () => {
+ it('should return diff for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_diff', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('diff');
+ });
+
+ it('should accept path and context parameters', async () => {
+ const result = await router.executeTool('get_pull_request_diff', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1,
+ path: 'src/test.ts',
+ context: 3
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+ });
+
+ describe('get_pull_request_patch', () => {
+ it('should return patch for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_patch', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('patch');
+ });
+ });
+
+ describe('get_pull_request_participants', () => {
+ it('should return participants for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_participants', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('values');
+ });
+ });
+
+ describe('get_pull_request_reviewers', () => {
+ it('should return reviewers for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_reviewers', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+ });
+
+ describe('get_pull_request_status', () => {
+ it('should return status for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_status', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('state');
+ });
+ });
+
+ describe('get_pull_request_tasks', () => {
+ it('should return tasks for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_tasks', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+ });
+
+ describe('get_pull_request_task_count', () => {
+ it('should return task count for valid PR', async () => {
+ const result = await router.executeTool('get_pull_request_task_count', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ });
+ });
+
+ describe('get_full_pull_request', () => {
+ it('should return full PR details', async () => {
+ const result = await router.executeTool('get_full_pull_request', {
+ workspace: 'test-workspace',
+ repository: 'test-repo',
+ pullRequestId: 1
+ }) as ToolResult;
+
+ expect(result.success).toBe(true);
+ expect(result.data).toHaveProperty('id');
+ expect(result.data).toHaveProperty('description');
+ });
+ });
+
+ describe('unknown tool', () => {
+ it('should return error for unknown tool', async () => {
+ const result = await router.executeTool('unknown_tool', {}) as ToolResult;
+
+ expect(result.success).toBe(false);
+ expect(result.error).toContain('Unknown tool');
+ });
+ });
+
+ describe('error handling', () => {
+ it('should handle API errors gracefully', async () => {
+ // Create router with a mock that throws
+ const errorRouter = new BitbucketRouter();
+
+ const result = await errorRouter.executeTool('list_pull_requests', {
+ workspace: 'test-workspace',
+ repository: 'test-repo'
+ }) as ToolResult;
+
+ // With our mock, this should succeed, but error cases should return proper format
+ if (!result.success) {
+ expect(result.error).toBeDefined();
+ expect(typeof result.error).toBe('string');
+ }
+ });
+ });
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..480df61
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "NodeNext",
+ "types": ["node"],
+ "moduleResolution": "NodeNext",
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..75d6db5
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,16 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ globals: true,
+ environment: 'node',
+ include: ['tests/unit/**/*.test.ts', 'tests/integration/**/*.test.ts'],
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json', 'html'],
+ exclude: ['node_modules/', 'dist/', 'tests/fixtures/']
+ },
+ testTimeout: 30000,
+ hookTimeout: 30000
+ }
+});