diff --git a/.gitignore b/.gitignore index 274ba2729..ef8526c56 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ tmp .yarn/cache .yarn/install-state.gz tsconfig.tsbuildinfo + +# MCP Worker build outputs +mcp-worker/dist/ +mcp-worker/.wrangler/ diff --git a/.nvmrc b/.nvmrc index 35d2d08ea..23222029f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.12 +22.* diff --git a/README.md b/README.md index c53e892be..afe0ee408 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -DevCycle CLI -================= +DevCycle CLI & MCP Server +========================= -DevCycle CLI for interacting with DevCycle features from the command line. +This repository contains the DevCycle CLI for managing feature flags from the command line, plus an MCP (Model Context Protocol) server that enables AI coding assistants to interact with DevCycle. Major features include: + - Fully manage your Features, Variables, Variations and Targeting Rules from the command line - Detect and list DevCycle Variable usages in your codebase - Manage your Self-Targeting Overrides to quickly switch between Variable values @@ -17,34 +18,84 @@ The CLI can be customized in several ways using command-line args or by creating [![License](https://img.shields.io/npm/l/@devcycle/cli.svg)](https://github.com/DevCycleHQ/cli/blob/main/package.json) -* [Setup](#setup) -* [Authentication](#authentication) -* [Usage](#usage) -* [Command Topics](#command-topics) -* [MCP Server for AI Assistants](#mcp-server-for-ai-assistants) -* [This installs both 'dvc' CLI and 'dvc-mcp' server](#this-installs-both-dvc-cli-and-dvc-mcp-server) -* [Access via: npx dvc-mcp](#access-via-npx-dvc-mcp) -* [Repo Configuration](#repo-configuration) +- [MCP Server for AI Assistants](#mcp-server-for-ai-assistants) +- [CLI Documentation](#cli-documentation) +- [Setup](#setup) +- [Authentication](#authentication) +- [Usage](#usage) +- [Command Topics](#command-topics) +- [Repo Configuration](#repo-configuration) -# Setup -## Install the CLI +## MCP Server for AI Assistants + +The DevCycle MCP (Model Context Protocol) server enables AI coding assistants like Cursor and Claude to manage feature flags directly from your development environment. DevCycle offers a hosted MCP server that requires no local installation. + +### Quick Setup (No Installation Required) + +1. **Configure your AI assistant to use the hosted MCP server:** + + - **Cursor**: Add to `.cursor/mcp_settings.json`: + + ```json + { + "mcpServers": { + "devcycle": { + "url": "https://mcp.devcycle.com/mcp" + } + } + } + ``` + + - **Claude Desktop**: Add to your Claude config file: + + ```json + { + "mcpServers": { + "devcycle": { + "command": "npx", + "args": [ + "mcp-remote", + "https://mcp.devcycle.com/mcp" + ] + } + } + } + ``` + +2. **That's it!** The server will guide you through OAuth authentication when you first use it. + +Your AI assistant can now create, update, and manage feature flags on your behalf. + +For local installation options, detailed configuration, available tools, and advanced usage, see the [complete MCP documentation](docs/mcp.md). + +## CLI Documentation + +## Setup + +### Install the CLI + Using NPM + ```sh-session $ npm install -g @devcycle/cli ``` + Or alternatively, using homebrew ```sh-session $ brew tap devcyclehq/cli $ brew install devcycle ``` -# Authentication + +## Authentication + Many of the CLI commands require DevCycle API authorization. There are several ways to provide these credentials. -## Using Access Tokens (preferred) +### Using Access Tokens (preferred) + +#### Login Command -### Login Command By using the [`login sso` command](docs/login.md#dvc-login-sso), the CLI will retrieve and store an access token, which is valid for 24 hours. The [`login again` command](docs/login.md#dvc-login-again) can be used to retrieve a new access token using the saved project and organization without prompting for them. @@ -55,12 +106,14 @@ To switch organizations once logged in, the [`organizations select` command](doc If executing the CLI in a containerized environment, please ensure one of the following PORTs can be accessed via Port Forwarding: 2194 (default), 2195, 2196 or 8080. This will allow the authentication process to complete and set the access token appropriately. -### Repo Init Command +#### Repo Init Command + The [`repo init` command](docs/repo.md#dvc-repo-init) behaves in the same way as `login sso`, but creates a [repo configuration file](#repo-configuration) and stores the project and organization choices there instead. -## Using Client Credentials +### Using Client Credentials + +#### Client Credentials in Auth File -### Client Credentials in Auth File Use the [`dvc status` command](docs/status.md#dvc-status) to find the configuration file location for your platform. The credentials can be stored in the file pointed to by the Auth config path. Create the file if it does not exist, with the following contents. ```yaml @@ -75,7 +128,7 @@ The default location is based on the [oclif configDir](https://oclif.io/docs/con If you intend to run the CLI using options that override config file locations, the [`dvc status` command](docs/status.md#dvc-status) command can be run with those options to confirm that the file locations are as expected. -## Project Selection +### Project Selection You also need to specify the default project ID for the CLI to use. @@ -83,7 +136,8 @@ If there is a repo configuration file, the [`dvc diff`](docs/diff.md) and [`dvc Otherwise, this is chosen during login or set using the [project select command](docs/projects.md#dvc-projects-select) -## Environment Variables +### Environment Variables + Set the following environment variables: ```sh-session @@ -92,7 +146,7 @@ $ export DEVCYCLE_CLIENT_SECRET= $ export DEVCYCLE_PROJECT_KEY= ``` -## Command-Line Arguments +### Command-Line Arguments The CLI can be run with the following arguments: @@ -100,11 +154,11 @@ The CLI can be run with the following arguments: $ dvc --client-id= --client-secret= --project= ``` -## Github Action +### Github Action The Devcycle Github actions are configured with auth information through the `project-key`, `client-id` and `client-secret` configuration parameters. This is passed to the CLI via command line arguments. -# Usage +## Usage ```sh-session @@ -112,7 +166,7 @@ $ npm install -g @devcycle/cli $ dvc COMMAND running command... $ dvc (--version) -@devcycle/cli/5.21.2 darwin-arm64 node-v22.12.0 +@devcycle/cli/5.21.2 darwin-arm64 node-v22.17.1 $ dvc --help [COMMAND] USAGE $ dvc COMMAND @@ -121,130 +175,33 @@ USAGE -# Command Topics - -* [`dvc alias`](docs/alias.md) - Manage repository variable aliases. -* [`dvc autocomplete`](docs/autocomplete.md) - display autocomplete installation instructions -* [`dvc cleanup`](docs/cleanup.md) - Replace a DevCycle variable with a static value in the current version of your code. Currently only JavaScript is supported. -* [`dvc diff`](docs/diff.md) - Print a diff of DevCycle variable usage between two versions of your code. -* [`dvc environments`](docs/environments.md) - Create a new Environment for an existing Feature. -* [`dvc features`](docs/features.md) - Create, view, or modify Features with the Management API. -* [`dvc generate`](docs/generate.md) - Generate Devcycle related files. -* [`dvc help`](docs/help.md) - Display help for dvc. -* [`dvc identity`](docs/identity.md) - View or manage your DevCycle Identity. -* [`dvc keys`](docs/keys.md) - Retrieve SDK keys from the Management API. -* [`dvc login`](docs/login.md) - Log in to DevCycle. -* [`dvc logout`](docs/logout.md) - Discards any auth configuration that has been stored in the auth configuration file. -* [`dvc organizations`](docs/organizations.md) - List or switch organizations. -* [`dvc overrides`](docs/overrides.md) - Create, view, or modify Overrides for a Project with the Management API. -* [`dvc projects`](docs/projects.md) - Create, or view Projects with the Management API. -* [`dvc repo`](docs/repo.md) - Manage repository configuration. -* [`dvc status`](docs/status.md) - Check CLI status. -* [`dvc targeting`](docs/targeting.md) - Create, view, or modify Targeting Rules for a Feature with the Management API. -* [`dvc usages`](docs/usages.md) - Print all DevCycle variable usages in the current version of your code. -* [`dvc variables`](docs/variables.md) - Create, view, or modify Variables with the Management API. -* [`dvc variations`](docs/variations.md) - Create a new Variation for an existing Feature. +## Command Topics + +- [`dvc alias`](docs/alias.md) - Manage repository variable aliases. +- [`dvc autocomplete`](docs/autocomplete.md) - display autocomplete installation instructions +- [`dvc cleanup`](docs/cleanup.md) - Replace a DevCycle variable with a static value in the current version of your code. Currently only JavaScript is supported. +- [`dvc diff`](docs/diff.md) - Print a diff of DevCycle variable usage between two versions of your code. +- [`dvc environments`](docs/environments.md) - Create a new Environment for an existing Feature. +- [`dvc features`](docs/features.md) - Create, view, or modify Features with the Management API. +- [`dvc generate`](docs/generate.md) - Generate Devcycle related files. +- [`dvc help`](docs/help.md) - Display help for dvc. +- [`dvc identity`](docs/identity.md) - View or manage your DevCycle Identity. +- [`dvc keys`](docs/keys.md) - Retrieve SDK keys from the Management API. +- [`dvc login`](docs/login.md) - Log in to DevCycle. +- [`dvc logout`](docs/logout.md) - Discards any auth configuration that has been stored in the auth configuration file. +- [`dvc organizations`](docs/organizations.md) - List or switch organizations. +- [`dvc overrides`](docs/overrides.md) - Create, view, or modify Overrides for a Project with the Management API. +- [`dvc projects`](docs/projects.md) - Create, or view Projects with the Management API. +- [`dvc repo`](docs/repo.md) - Manage repository configuration. +- [`dvc status`](docs/status.md) - Check CLI status. +- [`dvc targeting`](docs/targeting.md) - Create, view, or modify Targeting Rules for a Feature with the Management API. +- [`dvc usages`](docs/usages.md) - Print all DevCycle variable usages in the current version of your code. +- [`dvc variables`](docs/variables.md) - Create, view, or modify Variables with the Management API. +- [`dvc variations`](docs/variations.md) - Create a new Variation for an existing Feature. +## Repo Configuration -# MCP Server for AI Assistants - -The DevCycle CLI includes an MCP (Model Context Protocol) server that enables AI coding assistants like Cursor and Claude to manage feature flags directly. This allows you to create, update, and manage feature flags without leaving your coding environment. - -## Installation - -### Option 1: Global Installation (Recommended) -```bash -npm install -g @devcycle/cli -# This installs both 'dvc' CLI and 'dvc-mcp' server -``` - -### Option 2: Project-Specific Installation -```bash -npm install --save-dev @devcycle/cli -# Access via: npx dvc-mcp -``` - -### Verify Installation -```bash -dvc-mcp --version # Should display the DevCycle CLI version -dvc --version # Verify CLI is also installed -``` - -## Configuration - -### For Cursor -Add to `.cursor/mcp_settings.json`: -```json -{ - "mcpServers": { - "devcycle": { - "command": "dvc-mcp" - } - } -} -``` - -### For Claude Desktop -Add to your Claude configuration file: - -**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` -**Windows:** `%APPDATA%\Claude\claude_desktop_config.json` - -```json -{ - "mcpServers": { - "devcycle": { - "command": "dvc-mcp" - } - } -} -``` - -### For Project-Specific Installation -If you installed locally, update the command path: -```json -{ - "mcpServers": { - "devcycle": { - "command": "npx", - "args": ["dvc-mcp"] - } - } -} -``` - -## Authentication - -The MCP server uses the same authentication as the CLI: - -1. **Authenticate with DevCycle:** - ```bash - dvc login sso - ``` - -2. **Select your project:** - ```bash - dvc projects select - ``` - -3. **Verify setup:** - ```bash - dvc status - ``` - -Your AI assistant can now manage feature flags on your behalf. - -## Troubleshooting - -- **Command not found:** Ensure the CLI is installed globally or use `npx dvc-mcp` -- **Authentication errors:** Run `dvc login sso` to re-authenticate -- **No project selected:** Run `dvc projects select` to choose a project -- **Permission issues:** On Unix systems, you may need to restart your terminal after global installation - -For detailed documentation and advanced usage, see [docs/mcp.md](docs/mcp.md). - -# Repo Configuration The following commands can only be run from the root of a configured repository - [`dvc diff`](docs/diff.md) @@ -288,10 +245,11 @@ codeInsights: - "dist/*" ``` -## Match Patterns -When identifying variable usages in the code, the CLI will identify DevCycle SDK methods by default. To capture -other usages you may define match patterns. Match patterns are defined by file extension, and each pattern should -contain exactly one capture group which matches the key of the variable. Make sure the captured value contains the +### Match Patterns + +When identifying variable usages in the code, the CLI will identify DevCycle SDK methods by default. To capture +other usages you may define match patterns. Match patterns are defined by file extension, and each pattern should +contain exactly one capture group which matches the key of the variable. Make sure the captured value contains the entire key parameter (including quotes, if applicable). Match patterns can be defined in the configuration file, for example: @@ -312,11 +270,13 @@ codeInsights: ``` Match patterns can also be passed directly to relevant commands using the `--match-pattern` flag: -``` + +```bash dvc usages --match-pattern ts="customVariableGetter\(\s*[\"']([^\"']*)[\"']" js="customVariableGetter\(\s*[\"']([^\"']*)[\"']" ``` When testing your regex the `--show-regex` flag can be helpful. This will print all patterns used to find matches in your codebase. -``` + +```bash dvc usages --show-regex ``` diff --git a/docs/mcp.md b/docs/mcp.md index 1bda29388..eb19052cf 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4,6 +4,13 @@ The DevCycle MCP (Model Context Protocol) Server enables AI coding assistants like Cursor and Claude to interact directly with DevCycle's feature flag management system. This integration allows developers to manage feature flags, variables, and targeting rules without leaving their coding environment. +DevCycle provides two MCP implementations: + +- **Local MCP Server** (`dvc-mcp`): Runs locally using CLI authentication or API keys +- **Remote MCP Server** (`https://mcp.devcycle.com`): Hosted Cloudflare Worker with OAuth authentication + +Both implementations share the same underlying tools, schemas, and utilities, ensuring consistent functionality regardless of which option you choose. + ## Table of Contents - [Installation & Setup](#installation--setup) @@ -13,7 +20,6 @@ The DevCycle MCP (Model Context Protocol) Server enables AI coding assistants li - [Variable Management](#variable-management) - [Environment Management](#environment-management) - [Project Management](#project-management) - - [Custom Properties Management](#custom-properties-management) - [Self-Targeting & Overrides](#self-targeting--overrides) - [Results & Analytics](#results--analytics) - [Error Handling](#error-handling) @@ -22,15 +28,17 @@ The DevCycle MCP (Model Context Protocol) Server enables AI coding assistants li ## Installation & Setup +### Local MCP Server + ### Prerequisites - Node.js 16+ installed - DevCycle CLI installed globally: `npm install -g @devcycle/cli` - DevCycle account with API credentials or SSO authentication -### Cursor Configuration +### Local Configuration -Add to your Cursor settings (`.cursor/mcp_settings.json`): +For Cursor (`.cursor/mcp_settings.json`): ```json { @@ -42,9 +50,7 @@ Add to your Cursor settings (`.cursor/mcp_settings.json`): } ``` -### Claude Desktop Configuration - -Add to Claude's config file: +For Claude Desktop: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` @@ -59,11 +65,49 @@ Add to Claude's config file: } ``` +## Remote MCP Server (Cloudflare Worker) + +The remote MCP server provides OAuth-based authentication and requires no local installation. + +### Remote Configuration + +For Cursor (`.cursor/mcp_settings.json`): + +```json +{ + "mcpServers": { + "devcycle": { + "url": "https://mcp.devcycle.com/mcp" + } + } +} +``` + +For Claude Desktop: + +```json +{ + "mcpServers": { + "devcycle": { + "command": "npx", + "args": [ + "mcp-remote", + "https://mcp.devcycle.com/mcp" + ] + } + } +} +``` + +For more details about the remote MCP implementation, see the [MCP Worker README](../mcp-worker/README.md). + ## Authentication -The MCP server supports two authentication methods: +### Local MCP Server Authentication + +The local MCP server supports two authentication methods: -### 1. Environment Variables (Recommended for CI/CD) +#### 1. Environment Variables (Recommended for CI/CD) ```bash export DEVCYCLE_CLIENT_ID="your-client-id" @@ -71,38 +115,51 @@ export DEVCYCLE_CLIENT_SECRET="your-client-secret" export DEVCYCLE_PROJECT_KEY="your-project-key" ``` -### 2. CLI Authentication (Recommended for local development) - -First authenticate using the CLI: +#### 2. CLI Authentication (Recommended for local development) ```bash +# Authenticate via SSO dvc login sso -``` - -Then select your project: -```bash +# Select your project dvc projects select ``` -The MCP server will use the stored credentials automatically. +### Remote MCP Server Authentication + +The remote MCP server uses OAuth 2.0 authentication: + +- Users authenticate through DevCycle's Auth0 tenant +- No API keys or local credentials needed +- Project selection is handled through the `select_project` tool ## Available Tools +All tools listed below are available in both the local and remote MCP implementations. They share the same schemas, validation, and behavior. + +### Warning Symbols + +- **⚠️** - This tool can affect production environments. The AI will confirm with you before executing. +- **⚠️⚠️** - This tool performs destructive operations (deletions). The AI will require explicit confirmation before executing. + ### Feature Management #### `list_features` + List all features in the current project with optional search and pagination. **Parameters:** + - `search` (optional): Search query to filter features - `page` (optional): Page number (default: 1) - `per_page` (optional): Items per page (default: 100, max: 1000) #### `create_feature` ⚠️ + Create a new feature flag. **Parameters:** + - `key`: Unique feature key (pattern: `^[a-z0-9-_.]+$`) - `name`: Human-readable name (max 100 chars) - `description` (optional): Feature description (max 1000 chars) @@ -113,9 +170,11 @@ Create a new feature flag. - `sdkVisibility` (optional): SDK visibility settings #### `update_feature` ⚠️ + Update an existing feature flag. **Parameters:** + - `key`: Feature key to update - `name` (optional): New name - `description` (optional): New description @@ -124,38 +183,48 @@ Update an existing feature flag. - `variations` (optional): Updated variations #### `update_feature_status` ⚠️ + Update the status of a feature flag. **Parameters:** + - `key`: Feature key - `status`: New status (`active`, `complete`, `archived`) - `staticVariation` (optional): Variation to serve if status is `complete` #### `delete_feature` ⚠️⚠️ + Delete a feature flag from ALL environments. **Parameters:** + - `key`: Feature key to delete #### `fetch_feature_variations` + Get all variations for a feature. **Parameters:** + - `feature_key`: Feature key #### `create_feature_variation` + Create a new variation within a feature. **Parameters:** + - `feature_key`: Feature key - `key`: Unique variation key - `name`: Variation name - `variables` (optional): Variable values for this variation #### `update_feature_variation` + Update an existing variation by key. ⚠️ WARNING: Updating a feature variation may affect production environments. **Parameters:** + - `feature_key`: Feature key - `variation_key`: Variation to update - `_id` (optional): MongoDB ID for the variation @@ -163,57 +232,71 @@ Update an existing variation by key. ⚠️ WARNING: Updating a feature variatio - `name` (optional): New variation name - `variables` (optional): Updated variable values -#### `enable_feature_targeting` ⚠️ -Enable targeting for a feature in an environment. - -**Parameters:** -- `feature_key`: Feature key -- `environment_key`: Environment key +#### `set_feature_targeting` ⚠️ -#### `disable_feature_targeting` ⚠️ -Disable targeting for a feature in an environment. +Set targeting status (enable or disable) for a feature in an environment. **Parameters:** + - `feature_key`: Feature key - `environment_key`: Environment key +- `enabled`: Boolean - true to enable targeting, false to disable #### `list_feature_targeting` + List targeting rules for a feature. **Parameters:** + - `feature_key`: Feature key - `environment_key` (optional): Specific environment (returns all if omitted) #### `update_feature_targeting` ⚠️ + Update targeting rules for a feature in an environment. **Parameters:** + - `feature_key`: Feature key - `environment_key`: Environment key - `status` (optional): Targeting status (`active`, `inactive`, `archived`) - `targets` (optional): Array of targeting rules with audience filters and distributions #### `get_feature_audit_log_history` -Get timeline of feature flag changes from audit log. + +Get feature flag audit log history from DevCycle. Returns audit log entities matching the DevCycle API schema with date, a0_user, and changes fields. **Parameters:** + - `feature_key`: Feature key -- `days_back` (optional): Days to look back (default: 30, max: 365) +- `page` (optional): Page number for pagination (default: 1) +- `perPage` (optional): Number of items per page (default: 100, max: 1000) +- `sortBy` (optional): Field to sort by (`createdAt`, `updatedAt`, `action`, `user`) (default: `createdAt`) +- `sortOrder` (optional): Sort order (`asc`, `desc`) (default: `desc`) +- `startDate` (optional): Start date for filtering (ISO 8601 format) +- `endDate` (optional): End date for filtering (ISO 8601 format) +- `environment` (optional): Environment key to filter by +- `user` (optional): User ID to filter by +- `action` (optional): Action type to filter by ### Variable Management #### `list_variables` + List all variables in the current project. **Parameters:** + - `search` (optional): Search query - `page` (optional): Page number - `per_page` (optional): Items per page #### `create_variable` ⚠️ + Create a new variable. **Parameters:** + - `key`: Unique variable key (pattern: `^[a-z0-9-_.]+$`) - `type`: Variable type (`String`, `Boolean`, `Number`, `JSON`) - `name` (optional): Variable name @@ -223,9 +306,11 @@ Create a new variable. - `validationSchema` (optional): Validation rules #### `update_variable` ⚠️ + Update an existing variable. **Parameters:** + - `key`: Variable key to update - `name` (optional): New name - `description` (optional): New description @@ -233,17 +318,21 @@ Update an existing variable. - `validationSchema` (optional): New validation rules #### `delete_variable` ⚠️⚠️ + Delete a variable from ALL environments. **Parameters:** + - `key`: Variable key to delete ### Environment Management #### `list_environments` + List all environments in the current project. **Parameters:** + - `search` (optional): Search query (min 3 chars) - `page` (optional): Page number - `perPage` (optional): Items per page @@ -251,138 +340,84 @@ List all environments in the current project. - `sortOrder` (optional): Sort order (`asc`, `desc`) #### `get_sdk_keys` + Get SDK keys for an environment. **Parameters:** + - `environmentKey`: Environment key - `keyType` (optional): Specific key type (`mobile`, `server`, `client`) -#### `create_environment` -Create a new environment. - -**Parameters:** -- `key`: Unique environment key -- `name`: Environment name -- `description` (optional): Environment description -- `color` (optional): Environment color - -#### `update_environment` -Update an existing environment. - -**Parameters:** -- `key`: Environment key to update -- `name` (optional): New name -- `description` (optional): New description -- `color` (optional): New color - ### Project Management #### `list_projects` -List all projects in the organization. - -**Parameters:** -- `search` (optional): Search query -- `page` (optional): Page number -- `perPage` (optional): Items per page -- `sortBy` (optional): Sort field -- `sortOrder` (optional): Sort order - -#### `get_current_project` -Get details of the currently selected project. - -**Parameters:** None -#### `create_project` -Create a new project. +List all projects in the organization. **Parameters:** -- `key`: Unique project key -- `name`: Project name -- `description` (optional): Project description - -### Custom Properties Management - -#### `list_custom_properties` -List custom properties in the current project. -**Parameters:** -- `search` (optional): Search query to filter custom properties (min 3 chars) +- `search` (optional): Search query - `page` (optional): Page number (default: 1) - `perPage` (optional): Items per page (default: 100, max: 1000) -- `sortBy` (optional): Sort field (`createdAt`, `updatedAt`, `name`, `key`, `createdBy`, `propertyKey`) +- `sortBy` (optional): Sort field (`createdAt`, `updatedAt`, `name`, `key`, `createdBy`) - `sortOrder` (optional): Sort order (`asc`, `desc`) -- `createdBy` (optional): Filter by creator +- `createdBy` (optional): Filter by creator user ID -#### `create_custom_property` -Create a new custom property. - -**Parameters:** -- `key`: Unique property key (pattern: `^[a-z0-9-_.]+$`) -- `name`: Property name (max 100 chars) -- `type`: Property type (`String`, `Boolean`, `Number`) -- `propertyKey`: Property key used to identify the custom property in user data -- `schema` (optional): Schema definition with validation rules - -#### `update_custom_property` ⚠️ -Update an existing custom property. - -**Parameters:** -- `key`: Property key to update -- `name` (optional): New name -- `type` (optional): New type -- `propertyKey` (optional): New property key -- `schema` (optional): New schema definition +#### `get_current_project` -#### `delete_custom_property` ⚠️⚠️ -Delete a custom property from ALL environments. +Get details of the currently selected project. -**Parameters:** -- `key`: Property key to delete +**Parameters:** None ### Self-Targeting & Overrides #### `get_self_targeting_identity` + Get current DevCycle identity for self-targeting. **Parameters:** None #### `update_self_targeting_identity` + Update DevCycle identity for testing. **Parameters:** + - `dvc_user_id`: DevCycle User ID (use empty string to clear) #### `list_self_targeting_overrides` + List all active overrides for the current project. **Parameters:** None #### `set_self_targeting_override` ⚠️ + Set an override to test a specific variation. **Parameters:** + - `feature_key`: Feature key - `environment_key`: Environment key - `variation_key`: Variation to serve #### `clear_feature_self_targeting_overrides` ⚠️ + Clear overrides for a specific feature/environment. **Parameters:** + - `feature_key`: Feature key - `environment_key`: Environment key -#### `clear_all_self_targeting_overrides` -Clear all overrides for the current project. - -**Parameters:** None - ### Results & Analytics #### `get_feature_total_evaluations` + Get total variable evaluations per time period for a specific feature. **Parameters:** + - `featureKey`: Feature key - `startDate` (optional): Start date as Unix timestamp (milliseconds since epoch) - `endDate` (optional): End date as Unix timestamp (milliseconds since epoch) @@ -393,9 +428,11 @@ Get total variable evaluations per time period for a specific feature. - `sdkType` (optional): Filter by SDK type (`client`, `server`, `mobile`, `api`) #### `get_project_total_evaluations` + Get total variable evaluations per time period for the entire project. **Parameters:** + - `startDate` (optional): Start date as Unix timestamp (milliseconds since epoch) - `endDate` (optional): End date as Unix timestamp (milliseconds since epoch) - `platform` (optional): Platform filter for evaluation results @@ -418,6 +455,7 @@ The MCP server returns structured error responses: ``` Common error scenarios: + - Authentication failures: Check credentials and project configuration - API rate limits: Implement retry logic in your automation - Validation errors: Ensure parameters meet requirements (patterns, lengths, etc.) @@ -427,52 +465,46 @@ Common error scenarios: ### Creating a Feature Flag -``` +```text Create a feature flag for the new checkout flow with variations for A/B testing ``` The AI assistant will use: + 1. `create_feature` to create the feature 2. `create_feature_variation` to add variations 3. `enable_feature_targeting` to activate in development -### Managing Custom Properties - -``` -Create a custom property for user subscription tier with allowed values -``` - -The AI assistant will use: -1. `create_custom_property` with enum schema for allowed values -2. Configure the property with appropriate validation - ### Viewing Analytics -``` +```text Show me the evaluation metrics for the checkout_flow feature over the last week ``` The AI assistant will use: + 1. `get_feature_total_evaluations` with appropriate date range 2. Display the evaluation data grouped by time period ### Managing Overrides for Testing -``` +```text Set up my identity to test the premium user experience ``` The AI assistant will use: + 1. `update_self_targeting_identity` to set your user ID 2. `set_self_targeting_override` to force specific variations ### Analyzing Feature Usage -``` +```text Show me the recent changes to the checkout_flow feature ``` The AI assistant will use: + 1. `get_feature_audit_log_history` to retrieve change history 2. `list_feature_targeting` to show current configuration @@ -481,6 +513,7 @@ The AI assistant will use: ### 1. Production Safety Tools marked with ⚠️ can affect production environments. The AI will confirm before: + - Creating or updating features/variables - Enabling/disabling targeting - Setting overrides in production @@ -512,39 +545,20 @@ Tools marked with ⚠️⚠️ are destructive and require extra confirmation. - Tag features appropriately for organization - Include descriptions for documentation -## Limitations - -Current MCP server does NOT support: -- Code analysis tools (usage scanning, cleanup) -- Git integration features -- Type generation -- Analytics and metrics -- MCP Resources (read-only data access) -- MCP Prompts (guided workflows) - -These features are planned for future releases. +## Architecture & Implementation -## Troubleshooting +Both the local and remote MCP servers share: -### Authentication Issues +- **Common Tools**: All tool implementations in `src/mcp/tools/` +- **Schemas & Validation**: Shared Zod schemas for input/output validation +- **API Clients**: Both implement the `IDevCycleApiClient` interface +- **Error Handling**: Consistent error messages and handling across implementations -1. Verify environment variables are set correctly -2. Check CLI authentication: `dvc status` -3. Ensure project is selected: `dvc projects current` +Key differences: -### Connection Issues - -1. Check DevCycle CLI is installed: `dvc --version` -2. Verify MCP server starts: `dvc-mcp` -3. Check AI assistant logs for connection errors - -### API Errors - -1. Verify API credentials have necessary permissions -2. Check project and organization access -3. Review error messages for specific validation issues - -For additional support, visit [DevCycle Documentation](https://docs.devcycle.com) or contact support. +- **Authentication**: Local uses API keys/CLI auth, Remote uses OAuth 2.0 +- **Transport**: Local uses stdio, Remote uses SSE/HTTP +- **State Storage**: Local uses file system, Remote uses Durable Objects ## Development & Local Testing @@ -585,15 +599,6 @@ For local testing, update your AI assistant configuration to point to the local ### Debug Logging The MCP server logs all operations to stderr, which can be viewed in: + - Cursor: Developer Tools console - Claude Desktop: Log files in the application support directory - -### Environment Variables for Development - -```bash -# Enable verbose logging -export DEBUG=1 - -# Use specific DevCycle API endpoint -export DEVCYCLE_API_URL="https://api.devcycle.com" -``` \ No newline at end of file diff --git a/docs/mcp/MCP_ABSTRACTIONS_SUMMARY.md b/docs/mcp/MCP_ABSTRACTIONS_SUMMARY.md deleted file mode 100644 index f566e7497..000000000 --- a/docs/mcp/MCP_ABSTRACTIONS_SUMMARY.md +++ /dev/null @@ -1,136 +0,0 @@ -# DevCycle MCP Abstractions Summary - -This document provides a quick reference of all MCP abstractions for the DevCycle MCP server - both implemented and planned features. - -## Currently Implemented MCP Tools - -### 1. Feature Management -- `list_features` - List all features with search/pagination -- `create_feature` - Create new feature flag -- `update_feature` - Update existing feature -- `update_feature_status` - Update feature status (active/complete/archived) -- `delete_feature` - Delete a feature ⚠️ -- `fetch_feature_variations` - Get variations for a feature -- `create_feature_variation` - Create new variation -- `update_feature_variation` - Update variation properties ⚠️ -- `get_feature_audit_log_history` - Get timeline of feature changes - -### 2. Variable Management -- `list_variables` - List all variables -- `create_variable` - Create new variable -- `update_variable` - Update variable properties ⚠️ -- `delete_variable` - Delete a variable ⚠️ - -### 3. Environment Management -- `list_environments` - List project environments -- `get_sdk_keys` - Retrieve SDK keys for environment -- `create_environment` - Create new environment -- `update_environment` - Update environment settings - -### 4. Project & Organization -- `list_projects` - List all projects in organization -- `get_current_project` - Get currently selected project -- `create_project` - Create new project -- `update_project` - Update project settings - -### 5. Custom Properties Management -- `list_custom_properties` - List custom properties -- `create_custom_property` - Create new custom property -- `update_custom_property` - Update custom property ⚠️ -- `delete_custom_property` - Delete custom property ⚠️ - -### 6. Targeting Management -- `enable_feature_targeting` - Enable targeting for environment ⚠️ -- `disable_feature_targeting` - Disable targeting for environment ⚠️ -- `list_feature_targeting` - Get targeting rules for feature -- `update_feature_targeting` - Update targeting rules ⚠️ - -### 7. Self-Targeting & Override Management -- `get_self_targeting_identity` - Get current DevCycle identity -- `update_self_targeting_identity` - Update identity for testing -- `list_self_targeting_overrides` - List current overrides -- `set_self_targeting_override` - Set override for testing ⚠️ -- `clear_feature_self_targeting_overrides` - Clear specific overrides ⚠️ -- `clear_all_self_targeting_overrides` - Clear all overrides - -### 8. Results & Analytics -- `get_feature_total_evaluations` - Get feature evaluation metrics -- `get_project_total_evaluations` - Get project-wide evaluation metrics - -⚠️ = Requires confirmation for production environments - -## Planned Features (Not Yet Implemented) - -### MCP Resources (Read-only data access) -- `devcycle://config/repo` - Repository configuration -- `devcycle://config/auth` - Auth status -- `devcycle://project/features` - All features -- `devcycle://project/variables` - All variables -- `devcycle://project/environments` - All environments -- `devcycle://analysis/usages` - Latest usage scan -- `devcycle://analysis/types` - Generated types -- `devcycle://analysis/unknown-variables` - Undefined variables -- `devcycle://targeting/rules/{feature}` - Feature targeting rules -- `devcycle://overrides/current` - Active overrides -- `devcycle://environments/sdk-keys` - All SDK keys - -### MCP Prompts (Guided workflows) -- `create_feature_flag` - Guided feature creation -- `setup_targeting` - Configure targeting rules -- `implement_feature_flag` - Generate implementation code -- `analyze_feature_usage` - Comprehensive usage analysis -- `setup_testing_overrides` - QA testing configuration - -### Code Analysis Tools -- `analyze_variable_usage` - Comprehensive usage scanning -- `cleanup_variable` - Replace variable with static value -- `find_unknown_variables` - Identify undefined variables -- `get_feature_history` - Git history of feature flag usage - -### Type Generation -- `generate_typescript_types` - Generate TypeScript definitions - -### Advanced Targeting -- `get_audiences` - List reusable audience definitions -- `validate_targeting_rule` - Validate targeting before applying -- `create_targeting_rule` - Create new targeting rule (beyond basic enable/disable) - -### Environment Management (Advanced) -- `clone_environment` - Clone environment configuration - -### Advanced Analytics -- `get_variation_distribution` - Variation serve distribution -- `get_feature_usage_metrics` - Detailed usage statistics - -### Git Integration -- `analyze_pr_changes` - Analyze feature flag changes in PR -- `devcycle://git/feature-changes` - Recent changes -- `devcycle://git/pr-analysis` - PR flag analysis - -## Implementation Status - -### ✅ Implemented -- Core CRUD operations for features, variables, environments, projects -- Basic targeting enable/disable -- Self-targeting and override management -- Basic analytics (evaluation counts) -- Custom properties management - -### 🚧 In Progress -- None currently - -### 📋 Planned -- MCP Resources for read-only data access -- MCP Prompts for guided workflows -- Code analysis and usage scanning -- Type generation -- Advanced targeting with audience management -- Git integration and PR analysis -- Advanced analytics and metrics - -## Notes - -1. The current implementation focuses on direct API operations that modify DevCycle configuration -2. Code analysis features from the CLI (usages, cleanup, diff) are not yet exposed via MCP -3. MCP Resources and Prompts are part of the MCP specification but not yet implemented -4. All destructive operations include warnings and require user confirmation diff --git a/docs/mcp/MCP_PLANNING_DOCUMENT.md b/docs/mcp/MCP_PLANNING_DOCUMENT.md deleted file mode 100644 index a40165073..000000000 --- a/docs/mcp/MCP_PLANNING_DOCUMENT.md +++ /dev/null @@ -1,901 +0,0 @@ -# DevCycle CLI to MCP Server Planning Document - -## Executive Summary - -This document outlines the plan to transform the DevCycle CLI into a Model Context Protocol (MCP) server, enabling AI coding assistants like Cursor and Claude Code to seamlessly interact with DevCycle's feature flag management capabilities. - -## What is MCP? - -The Model Context Protocol (MCP) is an open standard developed by Anthropic that standardizes how AI applications connect to data sources and tools. It acts as a "USB-C port for AI applications," providing a universal way to connect AI models to external systems. - -### Key MCP Concepts -- **Hosts**: AI applications that initiate connections (e.g., Cursor, Claude Code) -- **Clients**: Connectors within the host application -- **Servers**: Services that provide context and capabilities -- **Protocol**: JSON-RPC 2.0 based communication - -### MCP Server Capabilities -1. **Tools**: Functions that can be called by the LLM (with user approval) -2. **Resources**: File-like data that can be read by clients -3. **Prompts**: Pre-written templates for common tasks - -## Current DevCycle CLI Analysis - -### Architecture -- Built with oclif framework and TypeScript -- Uses DevCycle Management API with authentication via client credentials or SSO -- Supports repository configuration with `.devcycle/config.yml` - -### Major Features -1. **Feature Management**: Create, view, modify Features, Variables, Variations, and Targeting Rules -2. **Code Analysis**: Detect DevCycle Variable usages in codebase -3. **Self-Targeting Overrides**: Manage overrides for quick testing -4. **Type Generation**: Generate TypeScript definitions -5. **Repository Integration**: Git-aware diff and usage detection - -### Key Command Categories -- `features`: CRUD operations on features -- `variables`: CRUD operations on variables -- `variations`: Manage feature variations -- `targeting`: Configure targeting rules -- `environments`: Manage environments -- `projects`: Project management -- `organizations`: Organization selection -- `overrides`: Self-targeting overrides -- `usages`: Detect variable usage in code -- `diff`: Show changes between code versions -- `cleanup`: Replace variables with static values -- `generate`: Generate type definitions - -## MCP Server Design - -### 1. Architecture Overview - -``` -AI Coding Assistant (Cursor/Claude Code) - ↓ (MCP Client) - MCP Server (DevCycle) - ↓ (HTTP/API calls) - DevCycle Management API -``` - -### 2. Transport Mechanism - -**Primary**: stdio transport for local development -- Communicates via stdin/stdout -- Launched by AI host applications -- No network configuration required - -**Future**: HTTP transport for remote deployment -- RESTful endpoints with Server-Sent Events (SSE) -- OAuth 2.0 authentication -- Scalable for team/enterprise use - -### 3. MCP Tools Design - -#### 3.1 Feature Management Tools - -**`list_features`** -- Description: List all features in a project -- Parameters: `search` (optional), `page` (optional), `per_page` (optional) -- Returns: Array of feature objects with keys, names, descriptions, and status - -**`get_feature`** -- Description: Get detailed information about a specific feature -- Parameters: `feature_key` (required) -- Returns: Complete feature object with variables, variations, and targeting - -**`create_feature`** -- Description: Create a new feature flag -- Parameters: `key`, `name`, `description`, `type`, `variations` -- Returns: Created feature object - -**`update_feature`** -- Description: Update an existing feature -- Parameters: `feature_key`, `name`, `description`, `variations` -- Returns: Updated feature object - -#### 3.2 Variable Management Tools - -**`list_variables`** -- Description: List all variables in a project -- Parameters: `search`, `page`, `per_page` -- Returns: Array of variable objects - -**`get_variable`** -- Description: Get detailed variable information -- Parameters: `variable_key` -- Returns: Variable object with type, default value, and variations - -**`create_variable`** -- Description: Create a new variable -- Parameters: `key`, `name`, `description`, `type`, `default_value` -- Returns: Created variable object - -#### 3.3 Code Analysis Tools - -**`scan_variable_usages`** -- Description: Scan codebase for DevCycle variable usage -- Parameters: `include_patterns`, `exclude_patterns`, `client_names`, `match_patterns` -- Returns: Usage report with file locations and variable references - -**`diff_variable_usage`** -- Description: Compare variable usage between code versions -- Parameters: `base_ref`, `head_ref`, `include_patterns`, `exclude_patterns` -- Returns: Diff report showing added/removed variable usage - -#### 3.4 Targeting and Environment Tools - -**`list_environments`** -- Description: List project environments -- Returns: Array of environment objects - -**`get_targeting_rules`** -- Description: Get targeting rules for a feature -- Parameters: `feature_key`, `environment_key` -- Returns: Targeting configuration - -**`update_targeting`** -- Description: Update targeting rules -- Parameters: `feature_key`, `environment_key`, `targeting_rules` -- Returns: Updated targeting configuration - -#### 3.5 Override Management Tools - -**`list_overrides`** -- Description: List current self-targeting overrides -- Returns: Array of active overrides - -**`set_override`** -- Description: Set a self-targeting override -- Parameters: `variable_key`, `value`, `environment_key` -- Returns: Override confirmation - -**`clear_overrides`** -- Description: Clear all or specific overrides -- Parameters: `variable_keys` (optional) -- Returns: Cleared override confirmation - -#### 3.6 Project and Organization Tools - -**`list_projects`** -- Description: List available projects -- Returns: Array of project objects - -**`get_current_project`** -- Description: Get currently selected project -- Returns: Current project information - -**`select_project`** -- Description: Switch to a different project -- Parameters: `project_key` -- Returns: Project selection confirmation - -### 3.7 Enhanced MCP Tools (Based on Codebase Analysis) - -Based on my comprehensive review of the DevCycle CLI codebase, I recommend the following enhanced and additional MCP tools: - -#### 3.7.1 Advanced Code Analysis Tools - -**`analyze_variable_usage`** -- Description: Comprehensive variable usage analysis with language-specific parsing -- Parameters: - - `include_patterns`: File glob patterns to include - - `exclude_patterns`: File glob patterns to exclude - - `client_names`: Additional SDK client names to detect - - `match_patterns`: Custom regex patterns by file extension - - `show_only_unknown`: Filter to show only unknown variables -- Returns: Detailed usage report with file locations, line numbers, and code context -- Note: Supports JavaScript, TypeScript, React, Python, Ruby, Go, Java, C#, PHP, Dart, iOS, and Android - -**`generate_usage_report`** -- Description: Generate a formatted usage report for documentation or review -- Parameters: - - `format`: Output format ('json', 'markdown', 'console') - - `output_file`: Optional file path for output -- Returns: Formatted usage report - -**`cleanup_variable`** -- Description: Replace DevCycle variable with static value in code -- Parameters: - - `variable_key`: Variable to replace - - `replacement_value`: Value to use as replacement - - `value_type`: Type of replacement value ('String', 'Boolean', 'Number', 'JSON') - - `include_patterns`: Files to include - - `exclude_patterns`: Files to exclude -- Returns: List of files modified with before/after preview - -#### 3.7.2 Identity and User Profile Tools - -**`get_identity`** -- Description: Get current DevCycle identity information -- Returns: User profile with identity settings - -**`update_identity`** -- Description: Update DevCycle identity for testing -- Parameters: - - `user_id`: SDK-associated user ID - - `email`: User email - - `name`: User name - - `country`: User country - - `custom_data`: Additional custom properties -- Returns: Updated identity information - -#### 3.7.3 Variation Management Tools - -**`list_variations`** -- Description: List all variations for a feature -- Parameters: `feature_key` -- Returns: Array of variation objects with keys and values - -**`create_variation`** -- Description: Create a new variation for a feature -- Parameters: - - `feature_key`: Feature to add variation to - - `key`: Unique variation key - - `name`: Human-readable name - - `variables`: Variable values for this variation -- Returns: Created variation object - -**`update_variation`** -- Description: Update an existing variation -- Parameters: - - `feature_key`: Feature containing the variation - - `variation_key`: Variation to update - - `name`: New name - - `variables`: Updated variable values -- Returns: Updated variation object - -#### 3.7.4 Advanced Targeting Tools - -**`create_targeting_rule`** -- Description: Create a new targeting rule with audience definition -- Parameters: - - `feature_key`: Feature for the rule - - `environment_key`: Environment to apply rule - - `name`: Rule name - - `audience_filters`: Audience definition filters - - `serve_variation`: Variation to serve - - `rollout_percentage`: Optional percentage rollout -- Returns: Created targeting rule - -**`get_audiences`** -- Description: List reusable audience definitions -- Returns: Array of audience objects with filters - -**`validate_targeting_rule`** -- Description: Validate a targeting rule before applying -- Parameters: - - `audience_filters`: Proposed audience definition - - `test_users`: Sample users to test against -- Returns: Validation results with matched users - -#### 3.7.5 Git Integration Tools - -**`analyze_pr_changes`** -- Description: Analyze feature flag changes in a pull request -- Parameters: - - `base_ref`: Base branch reference - - `head_ref`: Head branch reference - - `pr_link`: Optional PR link for enhanced formatting -- Returns: Summary of added/removed/modified feature flags - -**`get_feature_history`** -- Description: Get git history of feature flag usage -- Parameters: - - `feature_key`: Feature to analyze - - `days_back`: Number of days to look back -- Returns: Timeline of feature flag changes - -#### 3.7.6 Type Generation Tools - -**`generate_typescript_types`** -- Description: Generate TypeScript type definitions for features -- Parameters: - - `output_path`: Where to write the generated types - - `include_descriptions`: Include JSDoc comments -- Returns: Generated type definition content - -#### 3.7.7 Environment Management Tools - -**`clone_environment`** -- Description: Clone an environment with all settings -- Parameters: - - `source_environment`: Environment to clone from - - `new_key`: Key for new environment - - `new_name`: Name for new environment - - `include_targeting`: Whether to copy targeting rules -- Returns: Created environment object - -**`get_sdk_keys`** -- Description: Retrieve SDK keys for an environment -- Parameters: - - `environment_key`: Environment to get keys for - - `key_type`: Type of key ('mobile', 'server', 'client') -- Returns: SDK key information - -#### 3.7.8 Analytics and Metrics Tools - -**`get_feature_usage_metrics`** -- Description: Get usage metrics for a feature -- Parameters: - - `feature_key`: Feature to analyze - - `environment_key`: Optional environment filter - - `start_date`: Start of date range - - `end_date`: End of date range - - `period`: Aggregation period ('hour', 'day', 'month') -- Returns: Usage statistics and evaluation counts - -**`get_variation_distribution`** -- Description: Get distribution of variation serves -- Parameters: - - `feature_key`: Feature to analyze - - `environment_key`: Environment to analyze - - `time_range`: Time range for analysis -- Returns: Percentage distribution by variation - -### 4. MCP Resources Design - -#### 4.1 Configuration Resources - -**`config://repo`** -- URI: `devcycle://config/repo` -- Description: Current repository configuration -- Content: YAML configuration file content - -**`config://auth`** -- URI: `devcycle://config/auth` -- Description: Authentication status and configuration -- Content: Current auth state (without sensitive data) - -#### 4.2 Project Data Resources - -**`project://features`** -- URI: `devcycle://project/features` -- Description: All features in current project -- Content: JSON array of feature objects - -**`project://variables`** -- URI: `devcycle://project/variables` -- Description: All variables in current project -- Content: JSON array of variable objects - -#### 4.3 Code Analysis Resources - -**`analysis://usages`** -- URI: `devcycle://analysis/usages` -- Description: Latest variable usage scan results -- Content: JSON report of variable usage in codebase - -**`analysis://types`** -- URI: `devcycle://analysis/types` -- Description: Generated TypeScript type definitions -- Content: TypeScript definition file - -### 4.4 Enhanced Resources (Based on Codebase Analysis) - -Based on the codebase review, I recommend adding these resources: - -#### 4.4.1 Advanced Analysis Resources - -**`analysis://unknown-variables`** -- URI: `devcycle://analysis/unknown-variables` -- Description: Variables found in code but not defined in DevCycle -- Content: List of potentially orphaned or mistyped variables - -**`analysis://parser-patterns`** -- URI: `devcycle://analysis/parser-patterns` -- Description: Active regex patterns used for variable detection -- Content: Patterns organized by file extension with examples - -**`analysis://variable-aliases`** -- URI: `devcycle://analysis/variable-aliases` -- Description: Configured variable aliases from repo config -- Content: Mapping of code aliases to DevCycle variable keys - -#### 4.4.2 Targeting Resources - -**`targeting://rules/:feature`** -- URI: `devcycle://targeting/rules/{feature_key}` -- Description: All targeting rules for a specific feature -- Content: Targeting configuration across all environments - -**`targeting://active-rules`** -- URI: `devcycle://targeting/active-rules` -- Description: All active targeting rules in the project -- Content: Filtered list of enabled targeting rules - -#### 4.4.3 Override Resources - -**`overrides://current`** -- URI: `devcycle://overrides/current` -- Description: Current user's self-targeting overrides -- Content: Active overrides by feature and environment - -**`overrides://available`** -- URI: `devcycle://overrides/available` -- Description: Features available for override testing -- Content: List of features with their variations - -#### 4.4.4 Environment Resources - -**`environments://sdk-keys`** -- URI: `devcycle://environments/sdk-keys` -- Description: SDK keys for all environments -- Content: Keys organized by environment and SDK type - -**`environments://comparison`** -- URI: `devcycle://environments/comparison` -- Description: Side-by-side environment configuration comparison -- Content: Differences in features, variables, and targeting - -#### 4.4.5 Git Integration Resources - -**`git://feature-changes`** -- URI: `devcycle://git/feature-changes` -- Description: Recent git changes affecting feature flags -- Content: Commits and diffs with feature flag modifications - -**`git://pr-analysis`** -- URI: `devcycle://git/pr-analysis` -- Description: Feature flag analysis for current PR -- Content: Added/removed/modified flags with risk assessment - -### 5. MCP Prompts Design - -#### 5.1 Feature Management Prompts - -**`create_feature_flag`** -- Description: "Create a new feature flag with best practices" -- Template: Guides through feature creation with naming conventions -- Parameters: `feature_name`, `description`, `feature_type` - -**`setup_targeting`** -- Description: "Set up targeting rules for a feature" -- Template: Helps configure audience targeting -- Parameters: `feature_key`, `environment`, `targeting_strategy` - -#### 5.2 Code Integration Prompts - -**`implement_feature_flag`** -- Description: "Generate code to implement a feature flag" -- Template: Provides code snippets for different SDKs -- Parameters: `variable_key`, `language`, `default_value` - -**`cleanup_removed_flags`** -- Description: "Clean up removed feature flags from code" -- Template: Guides through safe flag removal process -- Parameters: `variable_keys`, `replacement_values` - -### 5.3 Enhanced Prompts (Based on Codebase Analysis) - -#### 5.3.1 Analysis and Review Prompts - -**`analyze_feature_usage`** -- Description: "Analyze how a feature flag is used in the codebase" -- Template: Comprehensive usage analysis with recommendations -- Parameters: `feature_key`, `include_git_history`, `check_dependencies` - -**`review_pr_flags`** -- Description: "Review feature flag changes in a pull request" -- Template: Systematic review checklist for flag changes -- Parameters: `pr_url`, `check_targeting`, `check_rollback_plan` - -**`find_stale_flags`** -- Description: "Identify feature flags that may be ready for removal" -- Template: Analysis of flag age, usage, and rollout status -- Parameters: `days_old`, `check_full_rollout`, `check_code_usage` - -#### 5.3.2 Testing and Override Prompts - -**`setup_testing_overrides`** -- Description: "Configure overrides for testing feature variations" -- Template: Step-by-step override setup for QA testing -- Parameters: `feature_key`, `test_scenarios`, `environments` - -**`create_test_matrix`** -- Description: "Generate test cases for feature flag variations" -- Template: Comprehensive test matrix generation -- Parameters: `feature_key`, `user_segments`, `include_edge_cases` - -#### 5.3.3 Migration and Refactoring Prompts - -**`migrate_feature_flags`** -- Description: "Migrate feature flags between projects or environments" -- Template: Safe migration process with validation steps -- Parameters: `source_project`, `target_project`, `features_to_migrate` - -**`refactor_flag_usage`** -- Description: "Refactor feature flag usage to follow best practices" -- Template: Code refactoring guide with patterns -- Parameters: `scan_directory`, `fix_patterns`, `update_aliases` - -#### 5.3.4 Rollout Strategy Prompts - -**`plan_progressive_rollout`** -- Description: "Plan a progressive feature rollout strategy" -- Template: Phased rollout planning with targeting rules -- Parameters: `feature_key`, `rollout_phases`, `success_metrics` - -**`create_killswitch`** -- Description: "Set up an emergency kill switch for a feature" -- Template: Rapid rollback configuration -- Parameters: `feature_key`, `alert_conditions`, `rollback_variation` - -#### 5.3.5 Documentation Prompts - -**`document_feature_flags`** -- Description: "Generate documentation for feature flags" -- Template: Auto-generated flag documentation -- Parameters: `output_format`, `include_examples`, `include_metrics` - -**`create_flag_runbook`** -- Description: "Create an operational runbook for a feature" -- Template: Operational procedures and troubleshooting -- Parameters: `feature_key`, `include_monitoring`, `include_rollback` - -## Implementation Considerations - -Based on my analysis of the DevCycle CLI codebase, here are key implementation considerations for the MCP server: - -### 1. Architecture Patterns - -**Leverage Existing Infrastructure** -- Reuse the existing command structure from `src/commands/` -- Utilize the Zod-based API client (`src/api/zodClient.ts`) for type safety -- Maintain compatibility with existing authentication mechanisms - -**Parser Reusability** -- The language-specific parsers in `src/utils/parsers/` are highly sophisticated -- Support for 12+ languages with custom regex patterns -- Can be directly integrated into MCP tools for code analysis - -**Configuration Management** -- Respect existing `.devcycle/config.yml` structure -- Support both repository and user-level configurations -- Handle variable aliases and custom match patterns - -### 2. Tool Implementation Strategy - -**Tool Categories by Priority** -1. **Core CRUD Operations** (Week 1-2) - - Direct mapping from existing commands - - Minimal transformation required - -2. **Code Analysis Tools** (Week 3-4) - - Leverage existing parser infrastructure - - Add MCP-specific formatting for results - -3. **Interactive Features** (Week 5-6) - - Transform prompt-based flows to parameter-based tools - - Maintain validation logic from interactive commands - -4. **Advanced Features** (Week 7-8) - - Git integration using existing diff utilities - - Analytics tools requiring new API integrations - -### 3. Authentication Architecture - -**Token Management** -- Reuse `TokenCache` and `ApiAuth` classes -- Support both SSO and client credentials -- Implement refresh logic for long-running sessions - -**Multi-Project Support** -- Allow project switching within MCP session -- Cache project-specific data appropriately -- Handle organization context switches - -### 4. Error Handling Patterns - -**Consistent Error Responses** -- Map CLI error types to MCP error codes -- Preserve detailed error messages from API -- Include actionable suggestions in error responses - -**Validation Layers** -- Client-side validation using Zod schemas -- API validation feedback -- File system and git operation errors - -### 5. Performance Optimizations - -**Caching Strategy** -- Cache feature and variable lists -- Implement smart cache invalidation -- Reuse parser results for repeated operations - -**Batch Operations** -- Use existing `batchRequests` utility -- Implement parallel processing for bulk operations -- Optimize file system scanning for large codebases - -### 6. Data Transformation - -**Response Formatting** -- Transform CLI table output to structured JSON -- Preserve tree structures for targeting rules -- Flatten nested objects for easier consumption - -**Input Normalization** -- Accept both keys and IDs for resources -- Implement fuzzy matching for user convenience -- Validate inputs against cached resource lists - -## Implementation Plan - -### Phase 1: Core MCP Server Setup (Week 1-2) -1. **Project Structure** - - Create new `src/mcp/` directory - - Set up MCP server entry point - - Configure TypeScript build for MCP server - -2. **Basic MCP Implementation** - - Implement stdio transport - - Set up capability negotiation - - Create base server class extending existing CLI base - -3. **Authentication Integration** - - Reuse existing authentication system - - Handle token management - - Project selection integration - -### Phase 2: Essential Tools Implementation (Week 3-4) -1. **Feature Management Tools** - - `list_features`, `get_feature` - - `create_feature`, `update_feature` - - Error handling and validation - -2. **Variable Management Tools** - - `list_variables`, `get_variable` - - `create_variable` - - Type safety and schema validation - -3. **Project Management Tools** - - `list_projects`, `get_current_project` - - `select_project` - -### Phase 3: Code Analysis Tools (Week 5-6) -1. **Usage Analysis** - - `scan_variable_usages` tool - - Reuse existing parsing logic - - File filtering and pattern matching - -2. **Diff Analysis** - - `diff_variable_usage` tool - - Git integration for version comparison - - Change detection and reporting - -### Phase 4: Advanced Features (Week 7-8) -1. **Targeting and Overrides** - - Targeting rule management tools - - Override management tools - - Environment-specific operations - -2. **Resources Implementation** - - Configuration resources - - Project data resources - - Analysis result resources - -### Phase 5: Prompts and Polish (Week 9-10) -1. **Prompt Templates** - - Feature creation prompts - - Code implementation prompts - - Best practice guidance - -2. **Testing and Documentation** - - Comprehensive testing - - Integration testing with Claude Code/Cursor - - Documentation and examples - -### Phase 6: HTTP Transport (Week 11-12) -1. **HTTP Server Implementation** - - Server-Sent Events support - - RESTful endpoint structure - - Session management - -2. **Authentication and Security** - - OAuth 2.0 integration - - API key authentication - - Rate limiting and security - -## Technical Implementation Details - -### 1. Project Structure -``` -src/ -├── mcp/ -│ ├── server.ts # Main MCP server implementation -│ ├── tools/ # MCP tool implementations -│ │ ├── features.ts -│ │ ├── variables.ts -│ │ ├── analysis.ts -│ │ └── index.ts -│ ├── resources/ # MCP resource implementations -│ │ ├── config.ts -│ │ ├── project.ts -│ │ └── index.ts -│ ├── prompts/ # MCP prompt templates -│ │ ├── feature-management.ts -│ │ ├── code-integration.ts -│ │ └── index.ts -│ ├── transports/ # Transport implementations -│ │ ├── stdio.ts -│ │ ├── http.ts -│ │ └── index.ts -│ └── index.ts # MCP server entry point -``` - -### 2. Dependencies -```json -{ - "dependencies": { - "@modelcontextprotocol/sdk": "latest", - "zod": "^3.24.2" - } -} -``` - -### 3. Configuration Integration -- Reuse existing `.devcycle/config.yml` structure -- Extend configuration for MCP-specific settings -- Maintain backward compatibility with CLI - -### 4. Error Handling Strategy -- Wrap API errors in MCP-compatible error responses -- Provide helpful error messages for common issues -- Graceful degradation when API is unavailable - -### 5. Performance Considerations -- Cache frequently accessed data (features, variables) -- Implement request batching for bulk operations -- Optimize file scanning for large codebases - -## Integration Examples - -### 1. Cursor Integration -```json -{ - "mcpServers": { - "devcycle": { - "command": "node", - "args": ["/path/to/devcycle-mcp-server/dist/mcp/index.js"] - } - } -} -``` - -### 2. Claude Code Integration -```bash -claude mcp add devcycle npx @devcycle/cli mcp-server -``` - -### 3. Usage Scenarios - -**Scenario 1: Creating a Feature Flag** -``` -User: "Create a feature flag for the new checkout flow" -AI: Uses create_feature tool → Creates feature with best practices -AI: Uses implement_feature_flag prompt → Generates implementation code -``` - -**Scenario 2: Code Review Analysis** -``` -User: "What feature flags are being added in this PR?" -AI: Uses diff_variable_usage tool → Analyzes code changes -AI: Provides summary of new feature flag usage -``` - -**Scenario 3: Cleanup Assistance** -``` -User: "Help me remove the old_checkout_flow feature flag" -AI: Uses scan_variable_usages → Finds all usage locations -AI: Uses cleanup_removed_flags prompt → Guides through removal -``` - -## Benefits and Value Proposition - -### For Developers -- **Seamless Integration**: Feature flag management directly in coding environment -- **Context-Aware Assistance**: AI understands current project state -- **Code Analysis**: Automatic detection of feature flag usage -- **Best Practices**: Built-in guidance for feature flag implementation - -### For Teams -- **Consistency**: Standardized feature flag practices across team -- **Visibility**: Easy access to feature flag status during code review -- **Efficiency**: Reduced context switching between tools -- **Documentation**: Automatic documentation of feature flag usage - -### For DevCycle -- **Market Expansion**: First feature flag platform with native AI integration -- **Developer Experience**: Superior DX compared to competitors -- **Ecosystem Growth**: Enable third-party integrations through MCP -- **Competitive Advantage**: Unique positioning in the market - -## Risk Assessment and Mitigation - -### Technical Risks -1. **MCP Protocol Changes**: Monitor MCP specification updates -2. **Performance Issues**: Implement caching and optimization -3. **Authentication Complexity**: Reuse proven CLI auth patterns - -### Product Risks -1. **User Adoption**: Provide clear documentation and examples -2. **Feature Completeness**: Prioritize most-used CLI features -3. **Maintenance Overhead**: Design for extensibility and maintainability - -### Mitigation Strategies -- Start with stdio transport for simplicity -- Reuse existing CLI codebase extensively -- Implement comprehensive testing -- Create detailed documentation and examples - -## Success Metrics - -### Technical Metrics -- MCP server response time < 500ms for most operations -- 99% uptime for HTTP transport -- Zero authentication-related security issues - -### Adoption Metrics -- Number of developers using MCP integration -- Frequency of MCP tool usage -- User satisfaction scores - -### Business Metrics -- Increased DevCycle API usage -- Improved developer onboarding time -- Positive feedback from AI coding assistant communities - -## Key Recommendations - -Based on the comprehensive codebase analysis, here are the critical recommendations for the DevCycle MCP server implementation: - -### 1. Maximize Code Reuse - -The existing CLI codebase is well-architected and can be heavily reused: -- **Direct Command Mapping**: Most CLI commands can be transformed into MCP tools with minimal changes -- **Parser Infrastructure**: The sophisticated language parsers are a competitive advantage - use them as-is -- **API Client**: The Zod-based client provides excellent type safety and validation - -### 2. Focus on Developer Workflows - -Prioritize tools that support common developer tasks: -1. **Quick Flag Creation**: Simple tool to create feature with sensible defaults -2. **Code Analysis**: Immediate feedback on flag usage in current code -3. **PR Review**: Automated analysis of feature flag changes in pull requests -4. **Test Override**: Easy variation testing without code changes -5. **Safe Cleanup**: Guided removal of obsolete flags - -### 3. Leverage AI Capabilities - -Design tools that benefit from AI context: -- **Smart Suggestions**: Let AI suggest flag names based on code context -- **Automated Documentation**: Generate flag documentation from usage patterns -- **Risk Assessment**: AI can evaluate the risk of flag changes -- **Test Scenarios**: Generate comprehensive test cases for variations - -### 4. Maintain Compatibility - -Ensure the MCP server works seamlessly with existing DevCycle ecosystem: -- Support existing `.devcycle/config.yml` format -- Use the same authentication mechanisms -- Maintain consistent naming conventions -- Preserve CLI command structure where logical - -### 5. Performance Considerations - -Optimize for responsive AI interactions: -- Implement aggressive caching for read operations -- Use batch APIs for bulk operations -- Pre-parse common file types on startup -- Stream large results for better UX - -## Conclusion - -Converting the DevCycle CLI into an MCP server represents a significant opportunity to pioneer AI-native developer tooling in the feature flag space. By leveraging the existing CLI codebase and MCP's standardized protocol, we can deliver a seamless experience that enhances developer productivity while maintaining DevCycle's position as an innovative platform. - -The phased implementation approach ensures manageable development cycles while delivering value incrementally. The focus on reusing existing CLI functionality minimizes risk while maximizing the potential for rapid deployment and adoption. - -This initiative positions DevCycle at the forefront of the AI-assisted development workflow revolution, providing a competitive advantage that will be difficult for competitors to replicate. \ No newline at end of file diff --git a/mcp-worker/.gitignore b/mcp-worker/.gitignore new file mode 100644 index 000000000..6fe79c1a2 --- /dev/null +++ b/mcp-worker/.gitignore @@ -0,0 +1,31 @@ +# Build outputs +dist/ +.wrangler/ + +# Dependencies +node_modules/ + +# Environment files +.env +.env.local +.env.production +.env.development +.dev.vars + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Temporary files +*.tmp +*.temp \ No newline at end of file diff --git a/mcp-worker/README.md b/mcp-worker/README.md new file mode 100644 index 000000000..f887cd748 --- /dev/null +++ b/mcp-worker/README.md @@ -0,0 +1,187 @@ +# DevCycle MCP Cloudflare Worker - Internal Documentation + +This package contains the DevCycle MCP (Model Context Protocol) server implementation that runs on Cloudflare Workers. It provides the same feature flag management tools as the local CLI MCP server but runs as a hosted service accessible via SSE (Server-Sent Events) at `mcp.devcycle.com`. + +## Overview for DevCycle Team + +This Worker enables DevCycle users to manage feature flags through AI assistants like Claude. It's a hosted alternative to the local CLI MCP server, providing: + +- **OAuth-based authentication** instead of API keys +- **Zero installation** for end users (just config) +- **Automatic updates** when we ship new features +- **Better security** through user-scoped tokens + +**Production URL**: `https://mcp.devcycle.com` +**Staging URL**: `https://devcycle-mcp-server-staging.devcycle.workers.dev` + +## Architecture + +- **Base Class**: Extends `McpAgent` from the `agents` package for MCP protocol handling +- **Main Class**: `DevCycleMCP` - Manages tool registration and state +- **Authentication**: OAuth 2.0 flow with Auth0 integration and consent screen +- **Transport**: Both SSE (`/sse`) and standard HTTP (`/mcp`) endpoints for MCP protocol +- **API Client**: `WorkerApiClient` - OAuth-based API client with state management +- **State Management**: Durable Objects for session and project selection persistence +- **Tool Registration**: Shared tools from CLI with Worker-specific adaptations + +## Production Configuration + +The MCP Worker is deployed to Cloudflare Workers on the `devcycle.com` zone at `mcp.devcycle.com`. + +### Infrastructure + +- **Worker Name**: `devcycle-mcp-server` +- **Production URL**: `https://mcp.devcycle.com` +- **Zone**: `devcycle.com` +- **Route Pattern**: `mcp.devcycle.com/*` + +### Storage + +- **KV Namespace**: `OAUTH_KV` - Stores OAuth session data +- **Durable Objects**: `DevCycleMCP` class - Maintains per-session state including project selection + +### Secrets (Configured in Cloudflare Dashboard) + +- `AUTH0_CLIENT_ID`: DevCycle's Auth0 application client ID +- `AUTH0_CLIENT_SECRET`: DevCycle's Auth0 application client secret + +## Local Development + +### Prerequisites + +- Node.js and Yarn installed +- Access to DevCycle's Cloudflare account +- Wrangler CLI: `npm install -g wrangler` + +### Running Locally + +1. Install dependencies: + + ```bash + yarn install + ``` + +2. Start the development server: + + ```bash + yarn dev + ``` + +This starts the Worker at `http://localhost:8787` with the following endpoints: + +- `/sse` - MCP Server-Sent Events endpoint +- `/mcp` - Standard HTTP MCP endpoint (request/response) +- `/oauth/authorize` - OAuth authorization endpoint +- `/oauth/callback` - OAuth callback endpoint +- `/oauth/consent` - Consent confirmation endpoint +- `/health` - Health check endpoint + +### Testing the Worker + +For local testing with Claude Desktop, configure: + +```json +{ + "mcpServers": { + "devcycle-local": { + "command": "npx", + "args": [ + "mcp-remote", + "http://localhost:8787/mcp" + ] + } + } +} +``` + +### User Onboarding Flow + +1. User adds the configuration above +2. Restarts Claude Desktop +3. On first connection, they'll be redirected to authenticate +4. After auth, they need to select a project with `select_project` +5. They can then use all DevCycle tools + +## Deployment + +### Production Deployment + +The production worker is deployed via: + +```bash +yarn deploy +``` + +This deploys to the `devcycle-mcp-server` worker with route `mcp.devcycle.com/*`. + +**Important**: Production deployments should be done through the CI/CD pipeline, not manually. + +## How It Works + +### Authentication Flow + +The Worker uses OAuth 2.0 flow integrated with DevCycle's Auth0 tenant: + +1. **Initial Connection**: AI client (Claude, Cursor, etc.) connects to the MCP server via SSE or HTTP +2. **Consent Screen**: Users see a DevCycle-branded consent screen showing: + - DevCycle logo and branding + - Requested permissions (profile, email, API access) + - Option to approve or deny access +3. **Auth0 Authentication**: Users authenticate via DevCycle's Auth0 tenant +4. **Token Exchange**: Worker exchanges authorization code for OAuth tokens +5. **JWT Claims**: The ID token contains DevCycle-specific claims: + - `org_id`: User's DevCycle organization ID + - `project_key`: Default project (if user has one configured) + - `email`: User's email address + - `name`: User's display name +6. **API Access**: MCP Worker uses the access token for DevCycle API calls on behalf of the user + +### Available Tools + +All standard DevCycle CLI MCP tools are available through the Worker. For detailed tool call definitions, parameters, and usage examples, see the [complete MCP tool reference](../docs/mcp.md#available-tools). + +#### Project Management + +- `select_project` - Select active project for subsequent operations +- `list_projects` - List all projects in organization +- `get_current_project` - Get currently selected project + +#### Feature Management + +- `list_features` - List features with filtering +- `create_feature` - Create new feature flag +- `update_feature` - Update feature configuration +- `update_feature_status` - Change feature status +- `delete_feature` - Delete feature flag +- `fetch_feature_variations` - Get feature variations +- `create_feature_variation` - Add new variation +- `update_feature_variation` - Update variation +- `set_feature_targeting` - Set targeting status (enable/disable) for feature +- `list_feature_targeting` - View feature targeting rules +- `update_feature_targeting` - Update feature targeting configuration + +#### Variable Management + +- `list_variables` - List variables with filtering +- `create_variable` - Create new variable +- `update_variable` - Update variable configuration +- `delete_variable` - Delete variable + +#### Environment Management + +- `list_environments` - List all environments +- `get_sdk_keys` - Get SDK keys for environment + +#### Targeting & Overrides + +- `get_self_targeting_identity` - Get current identity for testing +- `update_self_targeting_identity` - Set identity for testing +- `list_self_targeting_overrides` - List overrides +- `set_self_targeting_override` - Set override +- `clear_feature_self_targeting_overrides` - Clear specific overrides + +#### Analytics + +- `get_feature_total_evaluations` - Get feature usage metrics +- `get_project_total_evaluations` - Get project-wide metrics +- `get_feature_audit_log_history` - View change history diff --git a/mcp-worker/package.json b/mcp-worker/package.json new file mode 100644 index 000000000..25c6ba243 --- /dev/null +++ b/mcp-worker/package.json @@ -0,0 +1,24 @@ +{ + "name": "@devcycle/mcp-worker", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "build": "tsc", + "type-check": "tsc --noEmit", + "cf-typegen": "wrangler types" + }, + "dependencies": { + "@cloudflare/workers-oauth-provider": "^0.0.5", + "agents": "^0.0.100", + "hono": "^4.8.4", + "jose": "^6.0.11", + "oauth4webapi": "^3.5.5" + }, + "devDependencies": { + "wrangler": "^4.22.0" + }, + "packageManager": "yarn@4.9.2" +} diff --git a/mcp-worker/src/apiClient.ts b/mcp-worker/src/apiClient.ts new file mode 100644 index 000000000..714d45976 --- /dev/null +++ b/mcp-worker/src/apiClient.ts @@ -0,0 +1,219 @@ +import type { UserProps, DevCycleJWTClaims } from './types' +import { IDevCycleApiClient } from '../../dist/mcp/api/interface' + +/** + * Interface for state management - allows McpAgent or other state managers + */ +interface IStateManager { + state?: { selectedProjectKey?: string } + setState(newState: { selectedProjectKey?: string }): void +} + +/** + * Worker-specific API client implementation that uses OAuth tokens from JWT claims + * instead of API keys from local configuration files. + */ +export class WorkerApiClient implements IDevCycleApiClient { + constructor( + private props: UserProps, + private env: Env, + private stateManager?: IStateManager, + ) {} + + /** + * Execute an API operation with OAuth token authentication and consistent logging + */ + async executeWithLogging( + operationName: string, + args: any, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, + requiresProject: boolean = true, + ): Promise { + const authToken = this.getAuthToken() + const projectKey = await this.getProjectKey() + + if (requiresProject && !projectKey) { + throw new Error('No project key found') + } + + console.log(`Worker MCP ${operationName}:`, { + args, + userId: this.getUserId(), + orgId: this.getOrgId(), + projectKey: requiresProject ? projectKey : 'N/A', + }) + + try { + const result = await operation(authToken, projectKey) + console.log(`Worker MCP ${operationName} completed successfully`) + return result + } catch (error) { + console.error(`Worker MCP ${operationName} error:`, { + error: error instanceof Error ? error.message : String(error), + args, + userId: this.getUserId(), + orgId: this.getOrgId(), + }) + throw error + } + } + + /** + * Execute an API operation and include dashboard links in the response + */ + async executeWithDashboardLink( + operationName: string, + args: any, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, + dashboardLink: ( + orgId: string, + projectKey: string | undefined, + result: T, + ) => string, + ): Promise<{ result: T; dashboardLink: string }> { + const authToken = this.getAuthToken() + const projectKey = await this.getProjectKey() + const orgId = this.getOrgId() + + console.log(`Worker MCP ${operationName} (with dashboard link):`, { + args, + userId: this.getUserId(), + orgId, + projectKey, + }) + + try { + const result = await operation(authToken, projectKey) + const link = dashboardLink(orgId, projectKey, result) + + console.log( + `Worker MCP ${operationName} completed successfully with dashboard link`, + ) + + return { + result, + dashboardLink: link, + } + } catch (error) { + console.error(`Worker MCP ${operationName} error:`, { + error: error instanceof Error ? error.message : String(error), + args, + userId: this.getUserId(), + orgId, + }) + throw error + } + } + + /** + * Get the OAuth access token for API authentication + */ + private getAuthToken(): string { + if (!this.props.tokenSet?.accessToken) { + throw new Error('No access token available in user props') + } + return this.props.tokenSet.accessToken + } + + /** + * Get the project key from McpAgent state first, then fall back to JWT claims + */ + private async getProjectKey(): Promise { + // Priority 1: Check McpAgent state for selected project (if available) + try { + if (this.stateManager?.state?.selectedProjectKey) { + return this.stateManager.state.selectedProjectKey + } + } catch (error) { + console.warn('Failed to access state during getProjectKey:', error) + } + + // Priority 2: Fall back to JWT claims + const claims = this.props.claims as DevCycleJWTClaims + return claims?.project_key + } + + /** + * Set the selected project (stored in McpAgent state) + */ + async setSelectedProject(projectKey: string): Promise { + if (!this.stateManager) { + throw new Error( + 'Project selection not available - state management not initialized', + ) + } + + // Merge with existing state to preserve other state properties + this.stateManager.setState({ + ...(this.stateManager.state || {}), + selectedProjectKey: projectKey, + }) + } + + /** + * Check if user has a project key available (from storage or JWT claims) + */ + public async hasProjectKey(): Promise { + try { + const projectKey = await this.getProjectKey() + return !!projectKey + } catch { + return false + } + } + + /** + * Get the organization ID from JWT claims + */ + public getOrgId(): string { + const claims = this.props.claims as DevCycleJWTClaims + + if (claims?.org_id) { + return claims.org_id + } + + throw new Error('No organization ID found in JWT claims') + } + + /** + * Get the user ID from JWT claims (for logging purposes) + */ + public getUserId(): string { + const claims = this.props.claims as DevCycleJWTClaims + return claims?.sub || claims?.email || 'unknown' + } + + /** + * Check if the user has access to the required project + */ + public async hasProjectAccess(projectKey?: string): Promise { + try { + const userProjectKey = await this.getProjectKey() + return !projectKey || userProjectKey === projectKey + } catch { + return false + } + } + + /** + * Get user context information for debugging + */ + public async getUserContext() { + const claims = this.props.claims as DevCycleJWTClaims + + return { + userId: this.getUserId(), + orgId: this.getOrgId(), + email: claims?.email, + name: claims?.name, + hasAccessToken: !!this.props.tokenSet?.accessToken, + hasRefreshToken: !!this.props.tokenSet?.refreshToken, + } + } +} diff --git a/mcp-worker/src/auth.ts b/mcp-worker/src/auth.ts new file mode 100644 index 000000000..3ed157b73 --- /dev/null +++ b/mcp-worker/src/auth.ts @@ -0,0 +1,438 @@ +// Import removed - using env parameter instead +import { Hono } from 'hono' +import { getCookie, setCookie } from 'hono/cookie' +import * as oauth from 'oauth4webapi' +import type { UserProps } from './types' +import { OAuthHelpers } from '@cloudflare/workers-oauth-provider' +import type { + AuthRequest, + TokenExchangeCallbackOptions, + TokenExchangeCallbackResult, +} from '@cloudflare/workers-oauth-provider' +import { renderConsentScreen } from './consentScreen' + +type Auth0AuthRequest = { + mcpAuthRequest: AuthRequest + codeVerifier: string + codeChallenge: string + nonce: string + transactionState: string + consentToken: string +} + +export async function getOidcConfig({ + issuer, + client_id, + client_secret, +}: { + issuer: string + client_id: string + client_secret: string +}) { + // Validate required parameters + if (!issuer) { + throw new Error('AUTH0_DOMAIN is required but not set') + } + if (!client_id) { + throw new Error('AUTH0_CLIENT_ID is required but not set') + } + if (!client_secret) { + throw new Error('AUTH0_CLIENT_SECRET is required but not set') + } + + const response = await oauth.discoveryRequest(new URL(issuer), { + algorithm: 'oidc', + }) + console.log('Auth0 Discovery Response:', JSON.stringify(response)) + const as = await oauth.processDiscoveryResponse(new URL(issuer), response) + const client: oauth.Client = { client_id } + const clientAuth = oauth.ClientSecretPost(client_secret) + return { as, client, clientAuth } +} + +/** + * OAuth Authorization Endpoint + * + * This route initiates the Authorization Code Flow when a user wants to log in. + * It creates a random state parameter to prevent CSRF attacks and stores the + * original request information in a state-specific cookie for later retrieval. + * Then it shows a consent screen before redirecting to Auth0. + */ +export async function authorize( + c: any & { env: Env & { OAUTH_PROVIDER: OAuthHelpers } }, +) { + const mcpClientAuthRequest = await c.env.OAUTH_PROVIDER.parseAuthRequest( + c.req.raw, + ) + if (!mcpClientAuthRequest.clientId) { + return c.text('Invalid request', 400) + } + + const client = await c.env.OAUTH_PROVIDER.lookupClient( + mcpClientAuthRequest.clientId, + ) + if (!client) { + return c.text('Invalid client', 400) + } + + // Generate all that is needed for the Auth0 auth request + const codeVerifier = oauth.generateRandomCodeVerifier() + const transactionState = oauth.generateRandomState() + const consentToken = oauth.generateRandomState() // For CSRF protection on consent form + + // We will persist everything in a cookie. + const auth0AuthRequest: Auth0AuthRequest = { + codeChallenge: await oauth.calculatePKCECodeChallenge(codeVerifier), + codeVerifier, + consentToken, + mcpAuthRequest: mcpClientAuthRequest, + nonce: oauth.generateRandomNonce(), + transactionState, + } + + // Store the auth request in a transaction-specific cookie + const cookieName = `auth0_req_${transactionState}` + setCookie(c, cookieName, btoa(JSON.stringify(auth0AuthRequest)), { + httpOnly: true, + maxAge: 60 * 60 * 1, // 1 hour + path: '/', + sameSite: c.env.NODE_ENV !== 'development' ? 'none' : 'lax', + secure: c.env.NODE_ENV !== 'development', + }) + + // Extract client information for the consent screen + const clientName = client.clientName || client.clientId + const clientLogo = client.logoUri || '' // No default logo + const clientUri = client.clientUri || '#' + const requestedScopes = (c.env.AUTH0_SCOPE || '').split(' ') + + // Render the consent screen with CSRF protection + return c.html( + renderConsentScreen({ + clientLogo, + clientName, + clientUri, + consentToken, + redirectUri: mcpClientAuthRequest.redirectUri, + requestedScopes, + transactionState, + }), + ) +} + +/** + * Consent Confirmation Endpoint + * + * This route handles the consent confirmation before redirecting to Auth0 + */ +export async function confirmConsent(c: any) { + // Get form data + const formData = await c.req.formData() + console.log('Confirming consent') + console.log(JSON.stringify(formData)) + + const transactionState = formData.get('transaction_state') as string + const consentToken = formData.get('consent_token') as string + const consentAction = formData.get('consent_action') as string + + // Validate the transaction state + if (!transactionState) { + return c.text('Invalid transaction state', 400) + } + + // Get the transaction-specific cookie + const cookieName = `auth0_req_${transactionState}` + const auth0AuthRequestCookie = getCookie(c, cookieName) + if (!auth0AuthRequestCookie) { + return c.text('Invalid or expired transaction', 400) + } + + // Parse the Auth0 auth request from the cookie + console.log( + 'Auth0 Authorization Server - Transaction State Cookie:', + auth0AuthRequestCookie, + ) + const auth0AuthRequest = JSON.parse( + atob(auth0AuthRequestCookie), + ) as Auth0AuthRequest + + // Validate the CSRF token + if (auth0AuthRequest.consentToken !== consentToken) { + return c.text('Invalid consent token', 403) + } + + // Handle user denial + if (consentAction !== 'approve') { + // Parse the MCP client auth request to get the original redirect URI + const redirectUri = new URL(auth0AuthRequest.mcpAuthRequest.redirectUri) + + // Add error parameters to the redirect URI + redirectUri.searchParams.set('error', 'access_denied') + redirectUri.searchParams.set( + 'error_description', + 'User denied the request', + ) + if (auth0AuthRequest.mcpAuthRequest.state) { + redirectUri.searchParams.set( + 'state', + auth0AuthRequest.mcpAuthRequest.state, + ) + } + + // Clear the transaction cookie + setCookie(c, cookieName, '', { + maxAge: 0, + path: '/', + }) + + return c.redirect(redirectUri.toString()) + } + + const { as } = await getOidcConfig({ + client_id: c.env.AUTH0_CLIENT_ID, + client_secret: c.env.AUTH0_CLIENT_SECRET, + issuer: `https://${c.env.AUTH0_DOMAIN}/`, + }) + + console.log( + 'Auth0 Authorization Server - Discovery Information:', + JSON.stringify(as), + ) + + // Redirect to Auth0's authorization endpoint + const authorizationUrl = new URL(as.authorization_endpoint!) + authorizationUrl.searchParams.set('client_id', c.env.AUTH0_CLIENT_ID) + authorizationUrl.searchParams.set( + 'redirect_uri', + new URL('/oauth/callback', c.req.url).href, + ) + authorizationUrl.searchParams.set('response_type', 'code') + authorizationUrl.searchParams.set('audience', c.env.AUTH0_AUDIENCE) + authorizationUrl.searchParams.set('scope', c.env.AUTH0_SCOPE) + authorizationUrl.searchParams.set( + 'code_challenge', + auth0AuthRequest.codeChallenge, + ) + authorizationUrl.searchParams.set('code_challenge_method', 'S256') + authorizationUrl.searchParams.set('nonce', auth0AuthRequest.nonce) + authorizationUrl.searchParams.set('state', transactionState) + + return c.redirect(authorizationUrl.href) +} + +/** + * OAuth Callback Endpoint + * + * This route handles the callback from Auth0 after user authentication. + * It exchanges the authorization code for tokens and completes the + * authorization process. + */ +export async function callback( + c: any & { env: Env & { OAUTH_PROVIDER: OAuthHelpers } }, +) { + // Parse the state parameter to extract transaction state and Auth0 state + const stateParam = c.req.query('state') as string + if (!stateParam) { + return c.text('Invalid state parameter', 400) + } + + // Parse the Auth0 auth request from the transaction-specific cookie + const cookieName = `auth0_req_${stateParam}` + const auth0AuthRequestCookie = getCookie(c, cookieName) + if (!auth0AuthRequestCookie) { + return c.text('Invalid transaction state or session expired', 400) + } + + const auth0AuthRequest = JSON.parse( + atob(auth0AuthRequestCookie), + ) as Auth0AuthRequest + + // Clear the transaction cookie as it's no longer needed + setCookie(c, cookieName, '', { + maxAge: 0, + path: '/', + }) + + console.log( + 'Auth0 Authorization Server - Transaction State:', + auth0AuthRequest, + ) + + const { as, client, clientAuth } = await getOidcConfig({ + client_id: c.env.AUTH0_CLIENT_ID, + client_secret: c.env.AUTH0_CLIENT_SECRET, + issuer: `https://${c.env.AUTH0_DOMAIN}/`, + }) + + console.log( + 'Auth0 Authorization Server - Discovery Information:', + JSON.stringify(as), + JSON.stringify(clientAuth), + ) + + // Perform the Code Exchange + const params = oauth.validateAuthResponse( + as, + client, + new URL(c.req.url), + auth0AuthRequest.transactionState, + ) + + console.log('Response parameters from Auth0:', params) + + const response = await oauth.authorizationCodeGrantRequest( + as, + client, + clientAuth, + params, + new URL('/oauth/callback', c.req.url).href, + auth0AuthRequest.codeVerifier, + ) + + console.log('Response from Auth0 callback:', response) + + // Process the response + const result = await oauth.processAuthorizationCodeResponse( + as, + client, + response, + { + expectedNonce: auth0AuthRequest.nonce, + requireIdToken: true, + }, + ) + + // Get the claims from the id_token + const claims = oauth.getValidatedIdTokenClaims(result) + if (!claims) { + return c.text('Received invalid id_token from Auth0', 400) + } + + // Complete the authorization + const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({ + metadata: { + label: claims.name || claims.email || claims.sub, + }, + props: { + claims: claims, + tokenSet: { + accessToken: result.access_token, + accessTokenTTL: result.expires_in, + idToken: result.id_token, + refreshToken: result.refresh_token, + }, + } as UserProps, + request: auth0AuthRequest.mcpAuthRequest, + scope: auth0AuthRequest.mcpAuthRequest.scope, + userId: claims.sub!, + }) + + return Response.redirect(redirectTo, 302) +} + +/** + * Token Exchange Callback + * + * This function handles the token exchange callback for the CloudflareOAuth Provider + * and allows us to then interact with the Upstream IdP (your Auth0 tenant) + */ +export function createTokenExchangeCallback(env: Env) { + return async function tokenExchangeCallback( + options: TokenExchangeCallbackOptions, + ): Promise { + // During the Authorization Code Exchange, we want to make sure that the Access Token issued + // by the MCP Server has the same TTL as the one issued by Auth0. + if (options.grantType === 'authorization_code') { + return { + accessTokenTTL: options.props.tokenSet.accessTokenTTL, + newProps: { + ...options.props, + }, + } + } + + if (options.grantType === 'refresh_token') { + const auth0RefreshToken = options.props.tokenSet.refreshToken + if (!auth0RefreshToken) { + throw new Error('No Auth0 refresh token found') + } + + const { as, client, clientAuth } = await getOidcConfig({ + client_id: env.AUTH0_CLIENT_ID, + client_secret: env.AUTH0_CLIENT_SECRET, + issuer: `https://${env.AUTH0_DOMAIN}/`, + }) + + // Perform the refresh token exchange with Auth0. + const response = await oauth.refreshTokenGrantRequest( + as, + client, + clientAuth, + auth0RefreshToken, + ) + + const refreshTokenResponse = + await oauth.processRefreshTokenResponse(as, client, response) + + // Get the claims from the id_token + const claims = oauth.getValidatedIdTokenClaims(refreshTokenResponse) + if (!claims) { + throw new Error('Received invalid id_token from Auth0') + } + + // Store the new token set and claims. + return { + accessTokenTTL: refreshTokenResponse.expires_in, + newProps: { + ...options.props, + claims: claims, + tokenSet: { + accessToken: refreshTokenResponse.access_token, + accessTokenTTL: refreshTokenResponse.expires_in, + idToken: refreshTokenResponse.id_token, + refreshToken: + refreshTokenResponse.refresh_token || + auth0RefreshToken, + }, + }, + } + } + + throw new Error(`Unsupported grant type: ${options.grantType}`) + } +} + +/** + * Create the Hono app with OAuth and utility routes + */ +export function createAuthApp(): Hono<{ + Bindings: Env & { OAUTH_PROVIDER: OAuthHelpers } +}> { + const app = new Hono<{ Bindings: Env & { OAUTH_PROVIDER: OAuthHelpers } }>() + + // OAuth routes - these are required for the OAuth flow + app.get('/oauth/authorize', authorize) + app.post('/oauth/authorize/consent', confirmConsent) + app.get('/oauth/callback', callback) + + // Health check + app.get('/health', (c) => { + return c.json({ + status: 'ok', + service: 'DevCycle MCP Server', + timestamp: new Date().toISOString(), + }) + }) + + // Info endpoint for debugging + app.get('/info', (c) => { + return c.json({ + service: 'DevCycle MCP Server', + version: '1.0.0', + auth0Domain: c.env.AUTH0_DOMAIN, + apiBaseUrl: c.env.API_BASE_URL || 'https://api.devcycle.com', + }) + }) + + return app +} diff --git a/mcp-worker/src/consentScreen.ts b/mcp-worker/src/consentScreen.ts new file mode 100644 index 000000000..db11cf621 --- /dev/null +++ b/mcp-worker/src/consentScreen.ts @@ -0,0 +1,200 @@ +import { html, raw } from 'hono/html' + +/** + * Renders the consent screen HTML + */ +export function renderConsentScreen({ + clientName, + clientLogo, + clientUri, + redirectUri, + requestedScopes, + transactionState, + consentToken, +}: { + clientName: string + clientLogo: string + clientUri: string + redirectUri: string + requestedScopes: string[] + transactionState: string + consentToken: string +}) { + return html` + + + Authorization Request + + + + +
+ + +

DevCycle MCP Server - Authorization Request

+ +
+ ${clientName} +

+ ${clientName} is requesting + permission to access the + DevCycle API using your account. + Please review the permissions before proceeding. +

+
+ +
+

+ By clicking "Allow Access", you authorize + ${clientName} to access the + following resources: +

+
    + ${raw( + requestedScopes + .map((scope) => `
  • ${scope}
  • `) + .join('\n'), + )} +
+
+ +
+

+ If you did not initiate the request coming from + ${clientName} + (${redirectUri}) or you do not trust this + application, you should deny access. +

+
+ +
+ + +
+ + +
+
+ + +
+ + ` +} diff --git a/mcp-worker/src/index.ts b/mcp-worker/src/index.ts new file mode 100644 index 000000000..afd3a2d2f --- /dev/null +++ b/mcp-worker/src/index.ts @@ -0,0 +1,146 @@ +import OAuthProvider from '@cloudflare/workers-oauth-provider' +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { McpAgent } from 'agents/mcp' +import { createAuthApp, createTokenExchangeCallback } from './auth' +import { WorkerApiClient } from './apiClient' + +// Import the centralized registration function from CLI tools +import { registerAllToolsWithServer } from '../../src/mcp/tools/index' + +// Import types +import { DevCycleMCPServerInstance } from '../../src/mcp/server' +import { handleToolError } from '../../src/mcp/utils/errorHandling' + +import { registerProjectSelectionTools } from './projectSelectionTools' +import type { UserProps } from './types' + +/** + * State interface for DevCycle MCP Server + */ +type DevCycleMCPState = { + selectedProjectKey?: string +} + +/** + * DevCycle MCP Server for Cloudflare Workers + * + * This class extends McpAgent to provide DevCycle feature flag management + * through the Model Context Protocol over Server-Sent Events (SSE). + * It integrates OAuth authentication, tool registry, and Worker-specific API client. + */ +export class DevCycleMCP extends McpAgent { + server = new McpServer({ + name: 'DevCycle MCP Remote Server', + version: '1.0.0', + }) + + // Worker-specific API client that uses OAuth tokens + private apiClient: WorkerApiClient + + // Initial state for the MCP agent + initialState: DevCycleMCPState = { + selectedProjectKey: undefined, + } + + /** + * Initialize the MCP server with tools and handlers + */ + async init() { + // Initialize the Worker-specific API client with OAuth tokens and state management + this.apiClient = new WorkerApiClient( + this.props, + this.env, + this, // Pass the McpAgent instance for state management + ) + + console.log('Initializing DevCycle MCP Worker', { + userId: this.apiClient.getUserId(), + orgId: this.apiClient.getOrgId(), + hasProject: await this.apiClient.hasProjectKey(), + }) + + // Create an adapter to make the worker's McpServer compatible with the CLI registration pattern + const serverAdapter: DevCycleMCPServerInstance = { + registerToolWithErrorHandling: ( + name: string, + config: { + description: string + annotations?: any + inputSchema?: any + outputSchema?: any + }, + handler: (args: any) => Promise, + ) => { + this.server.registerTool( + name, + { + description: config.description, + annotations: config.annotations, + inputSchema: config.inputSchema || {}, + }, + async (args: any) => { + try { + const result = await handler(args) + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(result, null, 2), + }, + ], + } + } catch (error) { + return handleToolError(error, name) + } + }, + ) + }, + } + + // Register all CLI tools using the centralized registration function + // This automatically includes the same disabled tools as the main CLI MCP server + registerAllToolsWithServer(serverAdapter, this.apiClient) + + // Register worker-specific project selection tools using the modern pattern + registerProjectSelectionTools(serverAdapter, this.apiClient) + + console.log('✅ DevCycle MCP Worker initialization completed') + } + + /** + * Called when state updates (e.g., when project selection changes) + */ + onStateUpdate(state: DevCycleMCPState) { + console.log('DevCycle MCP State updated: ', state) + } +} + +// Export a fetch handler that creates the OAuth provider with proper env access +export default { + fetch( + request: Request, + env: Env, + ctx: ExecutionContext, + ): Promise { + // Initialize the Hono app with non-OAuth routes only + const app = createAuthApp() + + // Create OAuth provider with env access + const provider = new OAuthProvider({ + apiHandlers: { + // @ts-expect-error - type errors with the OAuthProvider + '/sse': DevCycleMCP.serveSSE('/sse'), + // @ts-expect-error - type errors with the OAuthProvider + '/mcp': DevCycleMCP.serve('/mcp'), + }, + // @ts-expect-error - type erorrs with the OAuthProvider + defaultHandler: app, + authorizeEndpoint: '/oauth/authorize', + tokenEndpoint: '/oauth/token', + clientRegistrationEndpoint: '/oauth/register', + tokenExchangeCallback: createTokenExchangeCallback(env), + }) + + return provider.fetch(request, env, ctx) + }, +} diff --git a/mcp-worker/src/projectSelectionTools.ts b/mcp-worker/src/projectSelectionTools.ts new file mode 100644 index 000000000..0fcfdd45d --- /dev/null +++ b/mcp-worker/src/projectSelectionTools.ts @@ -0,0 +1,130 @@ +import { z } from 'zod' +import { handleZodiosValidationErrors } from '../../src/mcp/utils/api' +import { fetchProjects, fetchProject } from '../../src/api/projects' +import { IDevCycleApiClient } from '../../src/mcp/api/interface' +import { DevCycleMCPServerInstance } from '../../src/mcp/server' + +// Helper functions to generate dashboard links +const generateProjectDashboardLink = ( + orgId: string, + projectKey: string | undefined, +): string => { + if (!projectKey) { + throw new Error( + 'Project key is required for project dashboard link. Please select a project using the select_project tool first.', + ) + } + return `https://app.devcycle.com/o/${orgId}/p/${projectKey}` +} + +const generateOrganizationProjectsLink = (orgId: string): string => { + return `https://app.devcycle.com/o/${orgId}/settings/projects` +} + +// ============================================================================= +// ZOD SCHEMAS +// ============================================================================= + +export const SelectProjectArgsSchema = z.object({ + projectKey: z + .string() + .optional() + .describe( + 'The project key to select (e.g., "jonathans-project"). If not provided, will list all available projects to choose from.', + ), +}) + +// ============================================================================= +// INDIVIDUAL HANDLER FUNCTIONS +// ============================================================================= + +export async function selectDevCycleProjectHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + console.log('select_project validatedArgs: ', args) + + // If no project key provided, list available projects + const projectKey = args.projectKey + console.log('select_project projectKey: ', projectKey) + + if (!projectKey) { + return await apiClient.executeWithDashboardLink( + 'listProjectsForSelection', + args, + async (authToken: string) => { + const projects = await handleZodiosValidationErrors( + () => + fetchProjects(authToken, { + page: 1, + perPage: 100, + sortBy: 'name', + sortOrder: 'asc', + }), + 'fetchProjects', + ) + + return { + availableProjects: projects.map((project: any) => ({ + key: project.key, + name: project.name, + description: project.description || '', + })), + message: + 'Available projects listed. Call this tool again with a project_key to select one.', + } + }, + generateOrganizationProjectsLink, + ) + } + + // Select the specified project + return await apiClient.executeWithDashboardLink( + 'selectProject', + args, + async (authToken: string) => { + // Fetch the specific project to verify it exists and user has access + const selectedProject = await handleZodiosValidationErrors( + () => fetchProject(authToken, projectKey), + 'fetchProject', + ) + + // Set the selected project in storage + await apiClient.setSelectedProject!(projectKey) + + return { + selectedProject: { + key: selectedProject.key, + name: selectedProject.name, + description: selectedProject.description || '', + }, + message: `Project '${selectedProject.name}' (${selectedProject.key}) has been selected for subsequent MCP operations.`, + } + }, + (orgId: string) => generateProjectDashboardLink(orgId, args.projectKey), + ) +} + +/** + * Register project selection tools with the MCP server using the new direct registration pattern + */ +export function registerProjectSelectionTools( + serverInstance: DevCycleMCPServerInstance, + apiClient: IDevCycleApiClient, +): void { + serverInstance.registerToolWithErrorHandling( + 'select_project', + { + description: + 'Select a project to use for subsequent MCP operations. Call without parameters to list available projects, or provide {"projectKey": "your-project-key"} to select a specific project. Include dashboard link in the response.', + annotations: { + title: 'Select Project', + }, + inputSchema: SelectProjectArgsSchema.shape, + }, + async (args: any) => { + const validatedArgs = SelectProjectArgsSchema.parse(args) + return await selectDevCycleProjectHandler(validatedArgs, apiClient) + }, + ) +} diff --git a/mcp-worker/src/types.ts b/mcp-worker/src/types.ts new file mode 100644 index 000000000..ac16352ed --- /dev/null +++ b/mcp-worker/src/types.ts @@ -0,0 +1,33 @@ +import type { JWTPayload } from 'jose' + +/** + * User properties for Cloudflare Worker MCP implementation + * Contains OAuth token information and JWT claims from DevCycle/Auth0 + */ +export type UserProps = { + /** JWT claims containing user identity and DevCycle context */ + claims: JWTPayload + /** OAuth token set for API authentication */ + tokenSet: { + accessToken: string + accessTokenTTL: number + idToken: string + refreshToken: string + } +} + +// Env interface is now generated in worker-configuration.d.ts + +/** + * Extended JWT claims that may contain DevCycle-specific information + */ +export interface DevCycleJWTClaims extends JWTPayload { + /** DevCycle organization ID */ + org_id?: string + /** DevCycle project key (if user has a default project) */ + project_key?: string + /** User's email */ + email?: string + /** User's name */ + name?: string +} diff --git a/mcp-worker/tsconfig.json b/mcp-worker/tsconfig.json new file mode 100644 index 000000000..9cb8d0a4b --- /dev/null +++ b/mcp-worker/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "../", + "types": [ + "node" + ], + "lib": ["ES2022"], + "module": "ESNext", + "target": "ES2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "declaration": false, + "declarationMap": false, + "sourceMap": true + }, + "include": [ + "src/**/*", + "../src/mcp/**/*", + "worker-configuration.d.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/mcp-worker/worker-configuration.d.ts b/mcp-worker/worker-configuration.d.ts new file mode 100644 index 000000000..7b20a66ec --- /dev/null +++ b/mcp-worker/worker-configuration.d.ts @@ -0,0 +1,7372 @@ +/* eslint-disable */ +// Generated by Wrangler by running `wrangler types` (hash: be45b0f19d86c17963edd55010e3b8be) +// Runtime types generated with workerd@1.20250712.0 2025-06-28 nodejs_compat +declare namespace Cloudflare { + interface Env { + OAUTH_KV: KVNamespace; + NODE_ENV: "production"; + API_BASE_URL: "https://api.devcycle.com"; + AUTH0_DOMAIN: "auth.devcycle.com"; + AUTH0_AUDIENCE: "https://api.devcycle.com/"; + AUTH0_SCOPE: "openid profile email offline_access"; + AUTH0_CLIENT_ID: string; + AUTH0_CLIENT_SECRET: string; + MCP_OBJECT: DurableObjectNamespace; + } +} +interface Env extends Cloudflare.Env {} +type StringifyValues> = { + [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; +}; +declare namespace NodeJS { + interface ProcessEnv extends StringifyValues> {} +} + +// Begin runtime types +/*! ***************************************************************************** +Copyright (c) Cloudflare. All rights reserved. +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +/* eslint-disable */ +// noinspection JSUnusedGlobalSymbols +declare var onmessage: never; +/** + * An abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException) + */ +declare class DOMException extends Error { + constructor(message?: string, name?: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message) */ + readonly message: string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name) */ + readonly name: string; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code) + */ + readonly code: number; + static readonly INDEX_SIZE_ERR: number; + static readonly DOMSTRING_SIZE_ERR: number; + static readonly HIERARCHY_REQUEST_ERR: number; + static readonly WRONG_DOCUMENT_ERR: number; + static readonly INVALID_CHARACTER_ERR: number; + static readonly NO_DATA_ALLOWED_ERR: number; + static readonly NO_MODIFICATION_ALLOWED_ERR: number; + static readonly NOT_FOUND_ERR: number; + static readonly NOT_SUPPORTED_ERR: number; + static readonly INUSE_ATTRIBUTE_ERR: number; + static readonly INVALID_STATE_ERR: number; + static readonly SYNTAX_ERR: number; + static readonly INVALID_MODIFICATION_ERR: number; + static readonly NAMESPACE_ERR: number; + static readonly INVALID_ACCESS_ERR: number; + static readonly VALIDATION_ERR: number; + static readonly TYPE_MISMATCH_ERR: number; + static readonly SECURITY_ERR: number; + static readonly NETWORK_ERR: number; + static readonly ABORT_ERR: number; + static readonly URL_MISMATCH_ERR: number; + static readonly QUOTA_EXCEEDED_ERR: number; + static readonly TIMEOUT_ERR: number; + static readonly INVALID_NODE_TYPE_ERR: number; + static readonly DATA_CLONE_ERR: number; + get stack(): any; + set stack(value: any); +} +type WorkerGlobalScopeEventMap = { + fetch: FetchEvent; + scheduled: ScheduledEvent; + queue: QueueEvent; + unhandledrejection: PromiseRejectionEvent; + rejectionhandled: PromiseRejectionEvent; +}; +declare abstract class WorkerGlobalScope extends EventTarget { + EventTarget: typeof EventTarget; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console) */ +interface Console { + "assert"(condition?: boolean, ...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static) */ + clear(): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static) */ + count(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static) */ + countReset(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static) */ + debug(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static) */ + dir(item?: any, options?: any): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static) */ + dirxml(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static) */ + error(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static) */ + group(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static) */ + groupCollapsed(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static) */ + groupEnd(): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static) */ + info(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static) */ + log(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static) */ + table(tabularData?: any, properties?: string[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static) */ + time(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static) */ + timeEnd(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static) */ + timeLog(label?: string, ...data: any[]): void; + timeStamp(label?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static) */ + trace(...data: any[]): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static) */ + warn(...data: any[]): void; +} +declare const console: Console; +type BufferSource = ArrayBufferView | ArrayBuffer; +type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array; +declare namespace WebAssembly { + class CompileError extends Error { + constructor(message?: string); + } + class RuntimeError extends Error { + constructor(message?: string); + } + type ValueType = "anyfunc" | "externref" | "f32" | "f64" | "i32" | "i64" | "v128"; + interface GlobalDescriptor { + value: ValueType; + mutable?: boolean; + } + class Global { + constructor(descriptor: GlobalDescriptor, value?: any); + value: any; + valueOf(): any; + } + type ImportValue = ExportValue | number; + type ModuleImports = Record; + type Imports = Record; + type ExportValue = Function | Global | Memory | Table; + type Exports = Record; + class Instance { + constructor(module: Module, imports?: Imports); + readonly exports: Exports; + } + interface MemoryDescriptor { + initial: number; + maximum?: number; + shared?: boolean; + } + class Memory { + constructor(descriptor: MemoryDescriptor); + readonly buffer: ArrayBuffer; + grow(delta: number): number; + } + type ImportExportKind = "function" | "global" | "memory" | "table"; + interface ModuleExportDescriptor { + kind: ImportExportKind; + name: string; + } + interface ModuleImportDescriptor { + kind: ImportExportKind; + module: string; + name: string; + } + abstract class Module { + static customSections(module: Module, sectionName: string): ArrayBuffer[]; + static exports(module: Module): ModuleExportDescriptor[]; + static imports(module: Module): ModuleImportDescriptor[]; + } + type TableKind = "anyfunc" | "externref"; + interface TableDescriptor { + element: TableKind; + initial: number; + maximum?: number; + } + class Table { + constructor(descriptor: TableDescriptor, value?: any); + readonly length: number; + get(index: number): any; + grow(delta: number, value?: any): number; + set(index: number, value?: any): void; + } + function instantiate(module: Module, imports?: Imports): Promise; + function validate(bytes: BufferSource): boolean; +} +/** + * This ServiceWorker API interface represents the global execution context of a service worker. + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope) + */ +interface ServiceWorkerGlobalScope extends WorkerGlobalScope { + DOMException: typeof DOMException; + WorkerGlobalScope: typeof WorkerGlobalScope; + btoa(data: string): string; + atob(data: string): string; + setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; + setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; + clearTimeout(timeoutId: number | null): void; + setInterval(callback: (...args: any[]) => void, msDelay?: number): number; + setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; + clearInterval(timeoutId: number | null): void; + queueMicrotask(task: Function): void; + structuredClone(value: T, options?: StructuredSerializeOptions): T; + reportError(error: any): void; + fetch(input: RequestInfo | URL, init?: RequestInit): Promise; + self: ServiceWorkerGlobalScope; + crypto: Crypto; + caches: CacheStorage; + scheduler: Scheduler; + performance: Performance; + Cloudflare: Cloudflare; + readonly origin: string; + Event: typeof Event; + ExtendableEvent: typeof ExtendableEvent; + CustomEvent: typeof CustomEvent; + PromiseRejectionEvent: typeof PromiseRejectionEvent; + FetchEvent: typeof FetchEvent; + TailEvent: typeof TailEvent; + TraceEvent: typeof TailEvent; + ScheduledEvent: typeof ScheduledEvent; + MessageEvent: typeof MessageEvent; + CloseEvent: typeof CloseEvent; + ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader; + ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader; + ReadableStream: typeof ReadableStream; + WritableStream: typeof WritableStream; + WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter; + TransformStream: typeof TransformStream; + ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy; + CountQueuingStrategy: typeof CountQueuingStrategy; + ErrorEvent: typeof ErrorEvent; + EventSource: typeof EventSource; + ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest; + ReadableStreamDefaultController: typeof ReadableStreamDefaultController; + ReadableByteStreamController: typeof ReadableByteStreamController; + WritableStreamDefaultController: typeof WritableStreamDefaultController; + TransformStreamDefaultController: typeof TransformStreamDefaultController; + CompressionStream: typeof CompressionStream; + DecompressionStream: typeof DecompressionStream; + TextEncoderStream: typeof TextEncoderStream; + TextDecoderStream: typeof TextDecoderStream; + Headers: typeof Headers; + Body: typeof Body; + Request: typeof Request; + Response: typeof Response; + WebSocket: typeof WebSocket; + WebSocketPair: typeof WebSocketPair; + WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair; + AbortController: typeof AbortController; + AbortSignal: typeof AbortSignal; + TextDecoder: typeof TextDecoder; + TextEncoder: typeof TextEncoder; + navigator: Navigator; + Navigator: typeof Navigator; + URL: typeof URL; + URLSearchParams: typeof URLSearchParams; + URLPattern: typeof URLPattern; + Blob: typeof Blob; + File: typeof File; + FormData: typeof FormData; + Crypto: typeof Crypto; + SubtleCrypto: typeof SubtleCrypto; + CryptoKey: typeof CryptoKey; + CacheStorage: typeof CacheStorage; + Cache: typeof Cache; + FixedLengthStream: typeof FixedLengthStream; + IdentityTransformStream: typeof IdentityTransformStream; + HTMLRewriter: typeof HTMLRewriter; +} +declare function addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void; +declare function removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void; +/** + * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) + */ +declare function dispatchEvent(event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap]): boolean; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */ +declare function btoa(data: string): string; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */ +declare function atob(data: string): string; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ +declare function setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ +declare function setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */ +declare function clearTimeout(timeoutId: number | null): void; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ +declare function setInterval(callback: (...args: any[]) => void, msDelay?: number): number; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ +declare function setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */ +declare function clearInterval(timeoutId: number | null): void; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */ +declare function queueMicrotask(task: Function): void; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */ +declare function structuredClone(value: T, options?: StructuredSerializeOptions): T; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */ +declare function reportError(error: any): void; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */ +declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise; +declare const self: ServiceWorkerGlobalScope; +/** +* The Web Crypto API provides a set of low-level functions for common cryptographic tasks. +* The Workers runtime implements the full surface of this API, but with some differences in +* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) +* compared to those implemented in most browsers. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) +*/ +declare const crypto: Crypto; +/** +* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) +*/ +declare const caches: CacheStorage; +declare const scheduler: Scheduler; +/** +* The Workers runtime supports a subset of the Performance API, used to measure timing and performance, +* as well as timing of subrequests and other operations. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) +*/ +declare const performance: Performance; +declare const Cloudflare: Cloudflare; +declare const origin: string; +declare const navigator: Navigator; +interface TestController { +} +interface ExecutionContext { + waitUntil(promise: Promise): void; + passThroughOnException(): void; + props: any; +} +type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; +type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise; +type ExportedHandlerScheduledHandler = (controller: ScheduledController, env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerQueueHandler = (batch: MessageBatch, env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTestHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise; +interface ExportedHandler { + fetch?: ExportedHandlerFetchHandler; + tail?: ExportedHandlerTailHandler; + trace?: ExportedHandlerTraceHandler; + tailStream?: ExportedHandlerTailStreamHandler; + scheduled?: ExportedHandlerScheduledHandler; + test?: ExportedHandlerTestHandler; + email?: EmailExportedHandler; + queue?: ExportedHandlerQueueHandler; +} +interface StructuredSerializeOptions { + transfer?: any[]; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent) */ +declare abstract class PromiseRejectionEvent extends Event { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise) */ + readonly promise: Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason) */ + readonly reason: any; +} +declare abstract class Navigator { + sendBeacon(url: string, body?: (ReadableStream | string | (ArrayBuffer | ArrayBufferView) | Blob | FormData | URLSearchParams | URLSearchParams)): boolean; + readonly userAgent: string; + readonly hardwareConcurrency: number; + readonly language: string; + readonly languages: string[]; +} +/** +* The Workers runtime supports a subset of the Performance API, used to measure timing and performance, +* as well as timing of subrequests and other operations. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) +*/ +interface Performance { + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */ + readonly timeOrigin: number; + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */ + now(): number; +} +interface AlarmInvocationInfo { + readonly isRetry: boolean; + readonly retryCount: number; +} +interface Cloudflare { + readonly compatibilityFlags: Record; +} +interface DurableObject { + fetch(request: Request): Response | Promise; + alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; + webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; + webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise; + webSocketError?(ws: WebSocket, error: unknown): void | Promise; +} +type DurableObjectStub = Fetcher & { + readonly id: DurableObjectId; + readonly name?: string; +}; +interface DurableObjectId { + toString(): string; + equals(other: DurableObjectId): boolean; + readonly name?: string; +} +interface DurableObjectNamespace { + newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId; + idFromName(name: string): DurableObjectId; + idFromString(id: string): DurableObjectId; + get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub; + jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace; +} +type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high"; +interface DurableObjectNamespaceNewUniqueIdOptions { + jurisdiction?: DurableObjectJurisdiction; +} +type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me"; +interface DurableObjectNamespaceGetDurableObjectOptions { + locationHint?: DurableObjectLocationHint; +} +interface DurableObjectState { + waitUntil(promise: Promise): void; + readonly id: DurableObjectId; + readonly storage: DurableObjectStorage; + container?: Container; + blockConcurrencyWhile(callback: () => Promise): Promise; + acceptWebSocket(ws: WebSocket, tags?: string[]): void; + getWebSockets(tag?: string): WebSocket[]; + setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void; + getWebSocketAutoResponse(): WebSocketRequestResponsePair | null; + getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null; + setHibernatableWebSocketEventTimeout(timeoutMs?: number): void; + getHibernatableWebSocketEventTimeout(): number | null; + getTags(ws: WebSocket): string[]; + abort(reason?: string): void; +} +interface DurableObjectTransaction { + get(key: string, options?: DurableObjectGetOptions): Promise; + get(keys: string[], options?: DurableObjectGetOptions): Promise>; + list(options?: DurableObjectListOptions): Promise>; + put(key: string, value: T, options?: DurableObjectPutOptions): Promise; + put(entries: Record, options?: DurableObjectPutOptions): Promise; + delete(key: string, options?: DurableObjectPutOptions): Promise; + delete(keys: string[], options?: DurableObjectPutOptions): Promise; + rollback(): void; + getAlarm(options?: DurableObjectGetAlarmOptions): Promise; + setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; + deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; +} +interface DurableObjectStorage { + get(key: string, options?: DurableObjectGetOptions): Promise; + get(keys: string[], options?: DurableObjectGetOptions): Promise>; + list(options?: DurableObjectListOptions): Promise>; + put(key: string, value: T, options?: DurableObjectPutOptions): Promise; + put(entries: Record, options?: DurableObjectPutOptions): Promise; + delete(key: string, options?: DurableObjectPutOptions): Promise; + delete(keys: string[], options?: DurableObjectPutOptions): Promise; + deleteAll(options?: DurableObjectPutOptions): Promise; + transaction(closure: (txn: DurableObjectTransaction) => Promise): Promise; + getAlarm(options?: DurableObjectGetAlarmOptions): Promise; + setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; + deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; + sync(): Promise; + sql: SqlStorage; + transactionSync(closure: () => T): T; + getCurrentBookmark(): Promise; + getBookmarkForTime(timestamp: number | Date): Promise; + onNextSessionRestoreBookmark(bookmark: string): Promise; +} +interface DurableObjectListOptions { + start?: string; + startAfter?: string; + end?: string; + prefix?: string; + reverse?: boolean; + limit?: number; + allowConcurrency?: boolean; + noCache?: boolean; +} +interface DurableObjectGetOptions { + allowConcurrency?: boolean; + noCache?: boolean; +} +interface DurableObjectGetAlarmOptions { + allowConcurrency?: boolean; +} +interface DurableObjectPutOptions { + allowConcurrency?: boolean; + allowUnconfirmed?: boolean; + noCache?: boolean; +} +interface DurableObjectSetAlarmOptions { + allowConcurrency?: boolean; + allowUnconfirmed?: boolean; +} +declare class WebSocketRequestResponsePair { + constructor(request: string, response: string); + get request(): string; + get response(): string; +} +interface AnalyticsEngineDataset { + writeDataPoint(event?: AnalyticsEngineDataPoint): void; +} +interface AnalyticsEngineDataPoint { + indexes?: ((ArrayBuffer | string) | null)[]; + doubles?: number[]; + blobs?: ((ArrayBuffer | string) | null)[]; +} +/** + * An event which takes place in the DOM. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event) + */ +declare class Event { + constructor(type: string, init?: EventInit); + /** + * Returns the type of event, e.g. "click", "hashchange", or "submit". + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type) + */ + get type(): string; + /** + * Returns the event's phase, which is one of NONE, CAPTURING_PHASE, AT_TARGET, and BUBBLING_PHASE. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase) + */ + get eventPhase(): number; + /** + * Returns true or false depending on how event was initialized. True if event invokes listeners past a ShadowRoot node that is the root of its target, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed) + */ + get composed(): boolean; + /** + * Returns true or false depending on how event was initialized. True if event goes through its target's ancestors in reverse tree order, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles) + */ + get bubbles(): boolean; + /** + * Returns true or false depending on how event was initialized. Its return value does not always carry meaning, but true can indicate that part of the operation during which event was dispatched, can be canceled by invoking the preventDefault() method. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable) + */ + get cancelable(): boolean; + /** + * Returns true if preventDefault() was invoked successfully to indicate cancelation, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented) + */ + get defaultPrevented(): boolean; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue) + */ + get returnValue(): boolean; + /** + * Returns the object whose event listener's callback is currently being invoked. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget) + */ + get currentTarget(): EventTarget | undefined; + /** + * Returns the object to which event is dispatched (its target). + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target) + */ + get target(): EventTarget | undefined; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement) + */ + get srcElement(): EventTarget | undefined; + /** + * Returns the event's timestamp as the number of milliseconds measured relative to the time origin. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp) + */ + get timeStamp(): number; + /** + * Returns true if event was dispatched by the user agent, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted) + */ + get isTrusted(): boolean; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) + */ + get cancelBubble(): boolean; + /** + * @deprecated + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) + */ + set cancelBubble(value: boolean); + /** + * Invoking this method prevents event from reaching any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any other objects. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation) + */ + stopImmediatePropagation(): void; + /** + * If invoked when the cancelable attribute value is true, and while executing a listener for the event with passive set to false, signals to the operation that caused event to be dispatched that it needs to be canceled. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault) + */ + preventDefault(): void; + /** + * When dispatched in a tree, invoking this method prevents event from reaching any objects other than the current object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation) + */ + stopPropagation(): void; + /** + * Returns the invocation target objects of event's path (objects on which listeners will be invoked), except for any nodes in shadow trees of which the shadow root's mode is "closed" that are not reachable from event's currentTarget. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath) + */ + composedPath(): EventTarget[]; + static readonly NONE: number; + static readonly CAPTURING_PHASE: number; + static readonly AT_TARGET: number; + static readonly BUBBLING_PHASE: number; +} +interface EventInit { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; +} +type EventListener = (event: EventType) => void; +interface EventListenerObject { + handleEvent(event: EventType): void; +} +type EventListenerOrEventListenerObject = EventListener | EventListenerObject; +/** + * EventTarget is a DOM interface implemented by objects that can receive events and may have listeners for them. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget) + */ +declare class EventTarget = Record> { + constructor(); + /** + * Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched. + * + * The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture. + * + * When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET. + * + * When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners. + * + * When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed. + * + * If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted. + * + * The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener) + */ + addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void; + /** + * Removes the event listener in target's event listener list with the same type, callback, and options. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener) + */ + removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void; + /** + * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) + */ + dispatchEvent(event: EventMap[keyof EventMap]): boolean; +} +interface EventTargetEventListenerOptions { + capture?: boolean; +} +interface EventTargetAddEventListenerOptions { + capture?: boolean; + passive?: boolean; + once?: boolean; + signal?: AbortSignal; +} +interface EventTargetHandlerObject { + handleEvent: (event: Event) => any | undefined; +} +/** + * A controller object that allows you to abort one or more DOM requests as and when desired. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController) + */ +declare class AbortController { + constructor(); + /** + * Returns the AbortSignal object associated with this object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal) + */ + get signal(): AbortSignal; + /** + * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort) + */ + abort(reason?: any): void; +} +/** + * A signal object that allows you to communicate with a DOM request (such as a Fetch) and abort it if required via an AbortController object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal) + */ +declare abstract class AbortSignal extends EventTarget { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static) */ + static abort(reason?: any): AbortSignal; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static) */ + static timeout(delay: number): AbortSignal; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static) */ + static any(signals: AbortSignal[]): AbortSignal; + /** + * Returns true if this AbortSignal's AbortController has signaled to abort, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted) + */ + get aborted(): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason) */ + get reason(): any; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ + get onabort(): any | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ + set onabort(value: any | null); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted) */ + throwIfAborted(): void; +} +interface Scheduler { + wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise; +} +interface SchedulerWaitOptions { + signal?: AbortSignal; +} +/** + * Extends the lifetime of the install and activate events dispatched on the global scope as part of the service worker lifecycle. This ensures that any functional events (like FetchEvent) are not dispatched until it upgrades database schemas and deletes the outdated cache entries. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent) + */ +declare abstract class ExtendableEvent extends Event { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil) */ + waitUntil(promise: Promise): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent) */ +declare class CustomEvent extends Event { + constructor(type: string, init?: CustomEventCustomEventInit); + /** + * Returns any custom data event was created with. Typically used for synthetic events. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail) + */ + get detail(): T; +} +interface CustomEventCustomEventInit { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; + detail?: any; +} +/** + * A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob) + */ +declare class Blob { + constructor(type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ + get size(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ + get type(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ + slice(start?: number, end?: number, type?: string): Blob; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer) */ + arrayBuffer(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes) */ + bytes(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ + text(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream) */ + stream(): ReadableStream; +} +interface BlobOptions { + type?: string; +} +/** + * Provides information about files and allows JavaScript in a web page to access their content. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File) + */ +declare class File extends Blob { + constructor(bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined, name: string, options?: FileOptions); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ + get name(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ + get lastModified(): number; +} +interface FileOptions { + type?: string; + lastModified?: number; +} +/** +* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) +*/ +declare abstract class CacheStorage { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open) */ + open(cacheName: string): Promise; + readonly default: Cache; +} +/** +* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) +*/ +declare abstract class Cache { + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */ + delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */ + match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; + /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */ + put(request: RequestInfo | URL, response: Response): Promise; +} +interface CacheQueryOptions { + ignoreMethod?: boolean; +} +/** +* The Web Crypto API provides a set of low-level functions for common cryptographic tasks. +* The Workers runtime implements the full surface of this API, but with some differences in +* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) +* compared to those implemented in most browsers. +* +* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) +*/ +declare abstract class Crypto { + /** + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle) + */ + get subtle(): SubtleCrypto; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues) */ + getRandomValues(buffer: T): T; + /** + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID) + */ + randomUUID(): string; + DigestStream: typeof DigestStream; +} +/** + * This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via Window.crypto). + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto) + */ +declare abstract class SubtleCrypto { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt) */ + encrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, key: CryptoKey, plainText: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt) */ + decrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, key: CryptoKey, cipherText: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign) */ + sign(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, data: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify) */ + verify(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, signature: ArrayBuffer | ArrayBufferView, data: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest) */ + digest(algorithm: string | SubtleCryptoHashAlgorithm, data: ArrayBuffer | ArrayBufferView): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey) */ + generateKey(algorithm: string | SubtleCryptoGenerateKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey) */ + deriveKey(algorithm: string | SubtleCryptoDeriveKeyAlgorithm, baseKey: CryptoKey, derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits) */ + deriveBits(algorithm: string | SubtleCryptoDeriveKeyAlgorithm, baseKey: CryptoKey, length?: number | null): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey) */ + importKey(format: string, keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey, algorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey) */ + exportKey(format: string, key: CryptoKey): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey) */ + wrapKey(format: string, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey) */ + unwrapKey(format: string, wrappedKey: ArrayBuffer | ArrayBufferView, unwrappingKey: CryptoKey, unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm, unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; + timingSafeEqual(a: ArrayBuffer | ArrayBufferView, b: ArrayBuffer | ArrayBufferView): boolean; +} +/** + * The CryptoKey dictionary of the Web Crypto API represents a cryptographic key. + * Available only in secure contexts. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey) + */ +declare abstract class CryptoKey { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type) */ + readonly type: string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable) */ + readonly extractable: boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm) */ + readonly algorithm: CryptoKeyKeyAlgorithm | CryptoKeyAesKeyAlgorithm | CryptoKeyHmacKeyAlgorithm | CryptoKeyRsaKeyAlgorithm | CryptoKeyEllipticKeyAlgorithm | CryptoKeyArbitraryKeyAlgorithm; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages) */ + readonly usages: string[]; +} +interface CryptoKeyPair { + publicKey: CryptoKey; + privateKey: CryptoKey; +} +interface JsonWebKey { + kty: string; + use?: string; + key_ops?: string[]; + alg?: string; + ext?: boolean; + crv?: string; + x?: string; + y?: string; + d?: string; + n?: string; + e?: string; + p?: string; + q?: string; + dp?: string; + dq?: string; + qi?: string; + oth?: RsaOtherPrimesInfo[]; + k?: string; +} +interface RsaOtherPrimesInfo { + r?: string; + d?: string; + t?: string; +} +interface SubtleCryptoDeriveKeyAlgorithm { + name: string; + salt?: (ArrayBuffer | ArrayBufferView); + iterations?: number; + hash?: (string | SubtleCryptoHashAlgorithm); + $public?: CryptoKey; + info?: (ArrayBuffer | ArrayBufferView); +} +interface SubtleCryptoEncryptAlgorithm { + name: string; + iv?: (ArrayBuffer | ArrayBufferView); + additionalData?: (ArrayBuffer | ArrayBufferView); + tagLength?: number; + counter?: (ArrayBuffer | ArrayBufferView); + length?: number; + label?: (ArrayBuffer | ArrayBufferView); +} +interface SubtleCryptoGenerateKeyAlgorithm { + name: string; + hash?: (string | SubtleCryptoHashAlgorithm); + modulusLength?: number; + publicExponent?: (ArrayBuffer | ArrayBufferView); + length?: number; + namedCurve?: string; +} +interface SubtleCryptoHashAlgorithm { + name: string; +} +interface SubtleCryptoImportKeyAlgorithm { + name: string; + hash?: (string | SubtleCryptoHashAlgorithm); + length?: number; + namedCurve?: string; + compressed?: boolean; +} +interface SubtleCryptoSignAlgorithm { + name: string; + hash?: (string | SubtleCryptoHashAlgorithm); + dataLength?: number; + saltLength?: number; +} +interface CryptoKeyKeyAlgorithm { + name: string; +} +interface CryptoKeyAesKeyAlgorithm { + name: string; + length: number; +} +interface CryptoKeyHmacKeyAlgorithm { + name: string; + hash: CryptoKeyKeyAlgorithm; + length: number; +} +interface CryptoKeyRsaKeyAlgorithm { + name: string; + modulusLength: number; + publicExponent: ArrayBuffer | ArrayBufferView; + hash?: CryptoKeyKeyAlgorithm; +} +interface CryptoKeyEllipticKeyAlgorithm { + name: string; + namedCurve: string; +} +interface CryptoKeyArbitraryKeyAlgorithm { + name: string; + hash?: CryptoKeyKeyAlgorithm; + namedCurve?: string; + length?: number; +} +declare class DigestStream extends WritableStream { + constructor(algorithm: string | SubtleCryptoHashAlgorithm); + readonly digest: Promise; + get bytesWritten(): number | bigint; +} +/** + * A decoder for a specific method, that is a specific character encoding, like utf-8, iso-8859-2, koi8, cp1261, gbk, etc. A decoder takes a stream of bytes as input and emits a stream of code points. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder) + */ +declare class TextDecoder { + constructor(label?: string, options?: TextDecoderConstructorOptions); + /** + * Returns the result of running encoding's decoder. The method can be invoked zero or more times with options's stream set to true, and then once without options's stream (or set to false), to process a fragmented input. If the invocation without options's stream (or set to false) has no input, it's clearest to omit both arguments. + * + * ``` + * var string = "", decoder = new TextDecoder(encoding), buffer; + * while(buffer = next_chunk()) { + * string += decoder.decode(buffer, {stream:true}); + * } + * string += decoder.decode(); // end-of-queue + * ``` + * + * If the error mode is "fatal" and encoding's decoder returns error, throws a TypeError. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode) + */ + decode(input?: (ArrayBuffer | ArrayBufferView), options?: TextDecoderDecodeOptions): string; + get encoding(): string; + get fatal(): boolean; + get ignoreBOM(): boolean; +} +/** + * TextEncoder takes a stream of code points as input and emits a stream of bytes. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder) + */ +declare class TextEncoder { + constructor(); + /** + * Returns the result of running UTF-8's encoder. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode) + */ + encode(input?: string): Uint8Array; + /** + * Runs the UTF-8 encoder on source, stores the result of that operation into destination, and returns the progress made as an object wherein read is the number of converted code units of source and written is the number of bytes modified in destination. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto) + */ + encodeInto(input: string, buffer: ArrayBuffer | ArrayBufferView): TextEncoderEncodeIntoResult; + get encoding(): string; +} +interface TextDecoderConstructorOptions { + fatal: boolean; + ignoreBOM: boolean; +} +interface TextDecoderDecodeOptions { + stream: boolean; +} +interface TextEncoderEncodeIntoResult { + read: number; + written: number; +} +/** + * Events providing information related to errors in scripts or in files. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent) + */ +declare class ErrorEvent extends Event { + constructor(type: string, init?: ErrorEventErrorEventInit); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename) */ + get filename(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message) */ + get message(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno) */ + get lineno(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno) */ + get colno(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error) */ + get error(): any; +} +interface ErrorEventErrorEventInit { + message?: string; + filename?: string; + lineno?: number; + colno?: number; + error?: any; +} +/** + * Provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data". + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData) + */ +declare class FormData { + constructor(); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) */ + append(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) */ + append(name: string, value: Blob, filename?: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete) */ + delete(name: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get) */ + get(name: string): (File | string) | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll) */ + getAll(name: string): (File | string)[]; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has) */ + has(name: string): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) */ + set(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) */ + set(name: string, value: Blob, filename?: string): void; + /* Returns an array of key, value pairs for every entry in the list. */ + entries(): IterableIterator<[ + key: string, + value: File | string + ]>; + /* Returns a list of keys in the list. */ + keys(): IterableIterator; + /* Returns a list of values in the list. */ + values(): IterableIterator<(File | string)>; + forEach(callback: (this: This, value: File | string, key: string, parent: FormData) => void, thisArg?: This): void; + [Symbol.iterator](): IterableIterator<[ + key: string, + value: File | string + ]>; +} +interface ContentOptions { + html?: boolean; +} +declare class HTMLRewriter { + constructor(); + on(selector: string, handlers: HTMLRewriterElementContentHandlers): HTMLRewriter; + onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter; + transform(response: Response): Response; +} +interface HTMLRewriterElementContentHandlers { + element?(element: Element): void | Promise; + comments?(comment: Comment): void | Promise; + text?(element: Text): void | Promise; +} +interface HTMLRewriterDocumentContentHandlers { + doctype?(doctype: Doctype): void | Promise; + comments?(comment: Comment): void | Promise; + text?(text: Text): void | Promise; + end?(end: DocumentEnd): void | Promise; +} +interface Doctype { + readonly name: string | null; + readonly publicId: string | null; + readonly systemId: string | null; +} +interface Element { + tagName: string; + readonly attributes: IterableIterator; + readonly removed: boolean; + readonly namespaceURI: string; + getAttribute(name: string): string | null; + hasAttribute(name: string): boolean; + setAttribute(name: string, value: string): Element; + removeAttribute(name: string): Element; + before(content: string | ReadableStream | Response, options?: ContentOptions): Element; + after(content: string | ReadableStream | Response, options?: ContentOptions): Element; + prepend(content: string | ReadableStream | Response, options?: ContentOptions): Element; + append(content: string | ReadableStream | Response, options?: ContentOptions): Element; + replace(content: string | ReadableStream | Response, options?: ContentOptions): Element; + remove(): Element; + removeAndKeepContent(): Element; + setInnerContent(content: string | ReadableStream | Response, options?: ContentOptions): Element; + onEndTag(handler: (tag: EndTag) => void | Promise): void; +} +interface EndTag { + name: string; + before(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; + after(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; + remove(): EndTag; +} +interface Comment { + text: string; + readonly removed: boolean; + before(content: string, options?: ContentOptions): Comment; + after(content: string, options?: ContentOptions): Comment; + replace(content: string, options?: ContentOptions): Comment; + remove(): Comment; +} +interface Text { + readonly text: string; + readonly lastInTextNode: boolean; + readonly removed: boolean; + before(content: string | ReadableStream | Response, options?: ContentOptions): Text; + after(content: string | ReadableStream | Response, options?: ContentOptions): Text; + replace(content: string | ReadableStream | Response, options?: ContentOptions): Text; + remove(): Text; +} +interface DocumentEnd { + append(content: string, options?: ContentOptions): DocumentEnd; +} +/** + * This is the event type for fetch events dispatched on the service worker global scope. It contains information about the fetch, including the request and how the receiver will treat the response. It provides the event.respondWith() method, which allows us to provide a response to this fetch. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent) + */ +declare abstract class FetchEvent extends ExtendableEvent { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request) */ + readonly request: Request; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */ + respondWith(promise: Response | Promise): void; + passThroughOnException(): void; +} +type HeadersInit = Headers | Iterable> | Record; +/** + * This Fetch API interface allows you to perform various actions on HTTP request and response headers. These actions include retrieving, setting, adding to, and removing. A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.  You can add to this using methods like append() (see Examples.) In all methods of this interface, header names are matched by case-insensitive byte sequence. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers) + */ +declare class Headers { + constructor(init?: HeadersInit); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get) */ + get(name: string): string | null; + getAll(name: string): string[]; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie) */ + getSetCookie(): string[]; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has) */ + has(name: string): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set) */ + set(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append) */ + append(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete) */ + delete(name: string): void; + forEach(callback: (this: This, value: string, key: string, parent: Headers) => void, thisArg?: This): void; + /* Returns an iterator allowing to go through all key/value pairs contained in this object. */ + entries(): IterableIterator<[ + key: string, + value: string + ]>; + /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */ + keys(): IterableIterator; + /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */ + values(): IterableIterator; + [Symbol.iterator](): IterableIterator<[ + key: string, + value: string + ]>; +} +type BodyInit = ReadableStream | string | ArrayBuffer | ArrayBufferView | Blob | URLSearchParams | FormData; +declare abstract class Body { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */ + get body(): ReadableStream | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ + get bodyUsed(): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ + arrayBuffer(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */ + bytes(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */ + text(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */ + json(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */ + formData(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ + blob(): Promise; +} +/** + * This Fetch API interface represents the response to a request. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) + */ +declare var Response: { + prototype: Response; + new (body?: BodyInit | null, init?: ResponseInit): Response; + error(): Response; + redirect(url: string, status?: number): Response; + json(any: any, maybeInit?: (ResponseInit | Response)): Response; +}; +/** + * This Fetch API interface represents the response to a request. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) + */ +interface Response extends Body { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone) */ + clone(): Response; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status) */ + status: number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText) */ + statusText: string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) */ + headers: Headers; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok) */ + ok: boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected) */ + redirected: boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url) */ + url: string; + webSocket: WebSocket | null; + cf: any | undefined; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type) */ + type: "default" | "error"; +} +interface ResponseInit { + status?: number; + statusText?: string; + headers?: HeadersInit; + cf?: any; + webSocket?: (WebSocket | null); + encodeBody?: "automatic" | "manual"; +} +type RequestInfo> = Request | string; +/** + * This Fetch API interface represents a resource request. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) + */ +declare var Request: { + prototype: Request; + new >(input: RequestInfo | URL, init?: RequestInit): Request; +}; +/** + * This Fetch API interface represents a resource request. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) + */ +interface Request> extends Body { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone) */ + clone(): Request; + /** + * Returns request's HTTP method, which is "GET" by default. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method) + */ + method: string; + /** + * Returns the URL of request as a string. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url) + */ + url: string; + /** + * Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers) + */ + headers: Headers; + /** + * Returns the redirect mode associated with request, which is a string indicating how redirects for the request will be handled during fetching. A request will follow redirects by default. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect) + */ + redirect: string; + fetcher: Fetcher | null; + /** + * Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort event handler. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal) + */ + signal: AbortSignal; + cf: Cf | undefined; + /** + * Returns request's subresource integrity metadata, which is a cryptographic hash of the resource being fetched. Its value consists of multiple hashes separated by whitespace. [SRI] + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity) + */ + integrity: string; + /** + * Returns a boolean indicating whether or not request can outlive the global in which it was created. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive) + */ + keepalive: boolean; + /** + * Returns the cache mode associated with request, which is a string indicating how the request will interact with the browser's cache when fetching. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache) + */ + cache?: "no-store"; +} +interface RequestInit { + /* A string to set request's method. */ + method?: string; + /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */ + headers?: HeadersInit; + /* A BodyInit object or null to set request's body. */ + body?: BodyInit | null; + /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ + redirect?: string; + fetcher?: (Fetcher | null); + cf?: Cf; + /* A string indicating how the request will interact with the browser's cache to set request's cache. */ + cache?: "no-store"; + /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */ + integrity?: string; + /* An AbortSignal to set request's signal. */ + signal?: (AbortSignal | null); + encodeResponseBody?: "automatic" | "manual"; +} +type Service = Fetcher; +type Fetcher = (T extends Rpc.EntrypointBranded ? Rpc.Provider : unknown) & { + fetch(input: RequestInfo | URL, init?: RequestInit): Promise; + connect(address: SocketAddress | string, options?: SocketOptions): Socket; +}; +interface KVNamespaceListKey { + name: Key; + expiration?: number; + metadata?: Metadata; +} +type KVNamespaceListResult = { + list_complete: false; + keys: KVNamespaceListKey[]; + cursor: string; + cacheStatus: string | null; +} | { + list_complete: true; + keys: KVNamespaceListKey[]; + cacheStatus: string | null; +}; +interface KVNamespace { + get(key: Key, options?: Partial>): Promise; + get(key: Key, type: "text"): Promise; + get(key: Key, type: "json"): Promise; + get(key: Key, type: "arrayBuffer"): Promise; + get(key: Key, type: "stream"): Promise; + get(key: Key, options?: KVNamespaceGetOptions<"text">): Promise; + get(key: Key, options?: KVNamespaceGetOptions<"json">): Promise; + get(key: Key, options?: KVNamespaceGetOptions<"arrayBuffer">): Promise; + get(key: Key, options?: KVNamespaceGetOptions<"stream">): Promise; + get(key: Array, type: "text"): Promise>; + get(key: Array, type: "json"): Promise>; + get(key: Array, options?: Partial>): Promise>; + get(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>; + get(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>; + list(options?: KVNamespaceListOptions): Promise>; + put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise; + getWithMetadata(key: Key, options?: Partial>): Promise>; + getWithMetadata(key: Key, type: "text"): Promise>; + getWithMetadata(key: Key, type: "json"): Promise>; + getWithMetadata(key: Key, type: "arrayBuffer"): Promise>; + getWithMetadata(key: Key, type: "stream"): Promise>; + getWithMetadata(key: Key, options: KVNamespaceGetOptions<"text">): Promise>; + getWithMetadata(key: Key, options: KVNamespaceGetOptions<"json">): Promise>; + getWithMetadata(key: Key, options: KVNamespaceGetOptions<"arrayBuffer">): Promise>; + getWithMetadata(key: Key, options: KVNamespaceGetOptions<"stream">): Promise>; + getWithMetadata(key: Array, type: "text"): Promise>>; + getWithMetadata(key: Array, type: "json"): Promise>>; + getWithMetadata(key: Array, options?: Partial>): Promise>>; + getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>>; + getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>>; + delete(key: Key): Promise; +} +interface KVNamespaceListOptions { + limit?: number; + prefix?: (string | null); + cursor?: (string | null); +} +interface KVNamespaceGetOptions { + type: Type; + cacheTtl?: number; +} +interface KVNamespacePutOptions { + expiration?: number; + expirationTtl?: number; + metadata?: (any | null); +} +interface KVNamespaceGetWithMetadataResult { + value: Value | null; + metadata: Metadata | null; + cacheStatus: string | null; +} +type QueueContentType = "text" | "bytes" | "json" | "v8"; +interface Queue { + send(message: Body, options?: QueueSendOptions): Promise; + sendBatch(messages: Iterable>, options?: QueueSendBatchOptions): Promise; +} +interface QueueSendOptions { + contentType?: QueueContentType; + delaySeconds?: number; +} +interface QueueSendBatchOptions { + delaySeconds?: number; +} +interface MessageSendRequest { + body: Body; + contentType?: QueueContentType; + delaySeconds?: number; +} +interface QueueRetryOptions { + delaySeconds?: number; +} +interface Message { + readonly id: string; + readonly timestamp: Date; + readonly body: Body; + readonly attempts: number; + retry(options?: QueueRetryOptions): void; + ack(): void; +} +interface QueueEvent extends ExtendableEvent { + readonly messages: readonly Message[]; + readonly queue: string; + retryAll(options?: QueueRetryOptions): void; + ackAll(): void; +} +interface MessageBatch { + readonly messages: readonly Message[]; + readonly queue: string; + retryAll(options?: QueueRetryOptions): void; + ackAll(): void; +} +interface R2Error extends Error { + readonly name: string; + readonly code: number; + readonly message: string; + readonly action: string; + readonly stack: any; +} +interface R2ListOptions { + limit?: number; + prefix?: string; + cursor?: string; + delimiter?: string; + startAfter?: string; + include?: ("httpMetadata" | "customMetadata")[]; +} +declare abstract class R2Bucket { + head(key: string): Promise; + get(key: string, options: R2GetOptions & { + onlyIf: R2Conditional | Headers; + }): Promise; + get(key: string, options?: R2GetOptions): Promise; + put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions & { + onlyIf: R2Conditional | Headers; + }): Promise; + put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions): Promise; + createMultipartUpload(key: string, options?: R2MultipartOptions): Promise; + resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload; + delete(keys: string | string[]): Promise; + list(options?: R2ListOptions): Promise; +} +interface R2MultipartUpload { + readonly key: string; + readonly uploadId: string; + uploadPart(partNumber: number, value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob, options?: R2UploadPartOptions): Promise; + abort(): Promise; + complete(uploadedParts: R2UploadedPart[]): Promise; +} +interface R2UploadedPart { + partNumber: number; + etag: string; +} +declare abstract class R2Object { + readonly key: string; + readonly version: string; + readonly size: number; + readonly etag: string; + readonly httpEtag: string; + readonly checksums: R2Checksums; + readonly uploaded: Date; + readonly httpMetadata?: R2HTTPMetadata; + readonly customMetadata?: Record; + readonly range?: R2Range; + readonly storageClass: string; + readonly ssecKeyMd5?: string; + writeHttpMetadata(headers: Headers): void; +} +interface R2ObjectBody extends R2Object { + get body(): ReadableStream; + get bodyUsed(): boolean; + arrayBuffer(): Promise; + bytes(): Promise; + text(): Promise; + json(): Promise; + blob(): Promise; +} +type R2Range = { + offset: number; + length?: number; +} | { + offset?: number; + length: number; +} | { + suffix: number; +}; +interface R2Conditional { + etagMatches?: string; + etagDoesNotMatch?: string; + uploadedBefore?: Date; + uploadedAfter?: Date; + secondsGranularity?: boolean; +} +interface R2GetOptions { + onlyIf?: (R2Conditional | Headers); + range?: (R2Range | Headers); + ssecKey?: (ArrayBuffer | string); +} +interface R2PutOptions { + onlyIf?: (R2Conditional | Headers); + httpMetadata?: (R2HTTPMetadata | Headers); + customMetadata?: Record; + md5?: ((ArrayBuffer | ArrayBufferView) | string); + sha1?: ((ArrayBuffer | ArrayBufferView) | string); + sha256?: ((ArrayBuffer | ArrayBufferView) | string); + sha384?: ((ArrayBuffer | ArrayBufferView) | string); + sha512?: ((ArrayBuffer | ArrayBufferView) | string); + storageClass?: string; + ssecKey?: (ArrayBuffer | string); +} +interface R2MultipartOptions { + httpMetadata?: (R2HTTPMetadata | Headers); + customMetadata?: Record; + storageClass?: string; + ssecKey?: (ArrayBuffer | string); +} +interface R2Checksums { + readonly md5?: ArrayBuffer; + readonly sha1?: ArrayBuffer; + readonly sha256?: ArrayBuffer; + readonly sha384?: ArrayBuffer; + readonly sha512?: ArrayBuffer; + toJSON(): R2StringChecksums; +} +interface R2StringChecksums { + md5?: string; + sha1?: string; + sha256?: string; + sha384?: string; + sha512?: string; +} +interface R2HTTPMetadata { + contentType?: string; + contentLanguage?: string; + contentDisposition?: string; + contentEncoding?: string; + cacheControl?: string; + cacheExpiry?: Date; +} +type R2Objects = { + objects: R2Object[]; + delimitedPrefixes: string[]; +} & ({ + truncated: true; + cursor: string; +} | { + truncated: false; +}); +interface R2UploadPartOptions { + ssecKey?: (ArrayBuffer | string); +} +declare abstract class ScheduledEvent extends ExtendableEvent { + readonly scheduledTime: number; + readonly cron: string; + noRetry(): void; +} +interface ScheduledController { + readonly scheduledTime: number; + readonly cron: string; + noRetry(): void; +} +interface QueuingStrategy { + highWaterMark?: (number | bigint); + size?: (chunk: T) => number | bigint; +} +interface UnderlyingSink { + type?: string; + start?: (controller: WritableStreamDefaultController) => void | Promise; + write?: (chunk: W, controller: WritableStreamDefaultController) => void | Promise; + abort?: (reason: any) => void | Promise; + close?: () => void | Promise; +} +interface UnderlyingByteSource { + type: "bytes"; + autoAllocateChunkSize?: number; + start?: (controller: ReadableByteStreamController) => void | Promise; + pull?: (controller: ReadableByteStreamController) => void | Promise; + cancel?: (reason: any) => void | Promise; +} +interface UnderlyingSource { + type?: "" | undefined; + start?: (controller: ReadableStreamDefaultController) => void | Promise; + pull?: (controller: ReadableStreamDefaultController) => void | Promise; + cancel?: (reason: any) => void | Promise; + expectedLength?: (number | bigint); +} +interface Transformer { + readableType?: string; + writableType?: string; + start?: (controller: TransformStreamDefaultController) => void | Promise; + transform?: (chunk: I, controller: TransformStreamDefaultController) => void | Promise; + flush?: (controller: TransformStreamDefaultController) => void | Promise; + cancel?: (reason: any) => void | Promise; + expectedLength?: number; +} +interface StreamPipeOptions { + /** + * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered. + * + * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. + * + * Errors and closures of the source and destination streams propagate as follows: + * + * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination. + * + * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source. + * + * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error. + * + * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source. + * + * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set. + */ + preventClose?: boolean; + preventAbort?: boolean; + preventCancel?: boolean; + signal?: AbortSignal; +} +type ReadableStreamReadResult = { + done: false; + value: R; +} | { + done: true; + value?: undefined; +}; +/** + * This Streams API interface represents a readable stream of byte data. The Fetch API offers a concrete instance of a ReadableStream through the body property of a Response object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) + */ +interface ReadableStream { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked) */ + get locked(): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel) */ + cancel(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) */ + getReader(): ReadableStreamDefaultReader; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) */ + getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough) */ + pipeThrough(transform: ReadableWritablePair, options?: StreamPipeOptions): ReadableStream; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo) */ + pipeTo(destination: WritableStream, options?: StreamPipeOptions): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee) */ + tee(): [ + ReadableStream, + ReadableStream + ]; + values(options?: ReadableStreamValuesOptions): AsyncIterableIterator; + [Symbol.asyncIterator](options?: ReadableStreamValuesOptions): AsyncIterableIterator; +} +/** + * This Streams API interface represents a readable stream of byte data. The Fetch API offers a concrete instance of a ReadableStream through the body property of a Response object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) + */ +declare const ReadableStream: { + prototype: ReadableStream; + new (underlyingSource: UnderlyingByteSource, strategy?: QueuingStrategy): ReadableStream; + new (underlyingSource?: UnderlyingSource, strategy?: QueuingStrategy): ReadableStream; +}; +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader) */ +declare class ReadableStreamDefaultReader { + constructor(stream: ReadableStream); + get closed(): Promise; + cancel(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read) */ + read(): Promise>; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock) */ + releaseLock(): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader) */ +declare class ReadableStreamBYOBReader { + constructor(stream: ReadableStream); + get closed(): Promise; + cancel(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read) */ + read(view: T): Promise>; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock) */ + releaseLock(): void; + readAtLeast(minElements: number, view: T): Promise>; +} +interface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions { + min?: number; +} +interface ReadableStreamGetReaderOptions { + /** + * Creates a ReadableStreamBYOBReader and locks the stream to the new reader. + * + * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle "bring your own buffer" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation. + */ + mode: "byob"; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest) */ +declare abstract class ReadableStreamBYOBRequest { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view) */ + get view(): Uint8Array | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond) */ + respond(bytesWritten: number): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView) */ + respondWithNewView(view: ArrayBuffer | ArrayBufferView): void; + get atLeast(): number | null; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController) */ +declare abstract class ReadableStreamDefaultController { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize) */ + get desiredSize(): number | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close) */ + close(): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue) */ + enqueue(chunk?: R): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error) */ + error(reason: any): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController) */ +declare abstract class ReadableByteStreamController { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest) */ + get byobRequest(): ReadableStreamBYOBRequest | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize) */ + get desiredSize(): number | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close) */ + close(): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue) */ + enqueue(chunk: ArrayBuffer | ArrayBufferView): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error) */ + error(reason: any): void; +} +/** + * This Streams API interface represents a controller allowing control of a WritableStream's state. When constructing a WritableStream, the underlying sink is given a corresponding WritableStreamDefaultController instance to manipulate. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController) + */ +declare abstract class WritableStreamDefaultController { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal) */ + get signal(): AbortSignal; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error) */ + error(reason?: any): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController) */ +declare abstract class TransformStreamDefaultController { + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize) */ + get desiredSize(): number | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue) */ + enqueue(chunk?: O): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error) */ + error(reason: any): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate) */ + terminate(): void; +} +interface ReadableWritablePair { + /** + * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use. + * + * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. + */ + writable: WritableStream; + readable: ReadableStream; +} +/** + * This Streams API interface provides a standard abstraction for writing streaming data to a destination, known as a sink. This object comes with built-in backpressure and queuing. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream) + */ +declare class WritableStream { + constructor(underlyingSink?: UnderlyingSink, queuingStrategy?: QueuingStrategy); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked) */ + get locked(): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort) */ + abort(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close) */ + close(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter) */ + getWriter(): WritableStreamDefaultWriter; +} +/** + * This Streams API interface is the object returned by WritableStream.getWriter() and once created locks the < writer to the WritableStream ensuring that no other streams can write to the underlying sink. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter) + */ +declare class WritableStreamDefaultWriter { + constructor(stream: WritableStream); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed) */ + get closed(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready) */ + get ready(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize) */ + get desiredSize(): number | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort) */ + abort(reason?: any): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close) */ + close(): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write) */ + write(chunk?: W): Promise; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock) */ + releaseLock(): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream) */ +declare class TransformStream { + constructor(transformer?: Transformer, writableStrategy?: QueuingStrategy, readableStrategy?: QueuingStrategy); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable) */ + get readable(): ReadableStream; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable) */ + get writable(): WritableStream; +} +declare class FixedLengthStream extends IdentityTransformStream { + constructor(expectedLength: number | bigint, queuingStrategy?: IdentityTransformStreamQueuingStrategy); +} +declare class IdentityTransformStream extends TransformStream { + constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy); +} +interface IdentityTransformStreamQueuingStrategy { + highWaterMark?: (number | bigint); +} +interface ReadableStreamValuesOptions { + preventCancel?: boolean; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream) */ +declare class CompressionStream extends TransformStream { + constructor(format: "gzip" | "deflate" | "deflate-raw"); +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream) */ +declare class DecompressionStream extends TransformStream { + constructor(format: "gzip" | "deflate" | "deflate-raw"); +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream) */ +declare class TextEncoderStream extends TransformStream { + constructor(); + get encoding(): string; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream) */ +declare class TextDecoderStream extends TransformStream { + constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit); + get encoding(): string; + get fatal(): boolean; + get ignoreBOM(): boolean; +} +interface TextDecoderStreamTextDecoderStreamInit { + fatal?: boolean; + ignoreBOM?: boolean; +} +/** + * This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy) + */ +declare class ByteLengthQueuingStrategy implements QueuingStrategy { + constructor(init: QueuingStrategyInit); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark) */ + get highWaterMark(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */ + get size(): (chunk?: any) => number; +} +/** + * This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy) + */ +declare class CountQueuingStrategy implements QueuingStrategy { + constructor(init: QueuingStrategyInit); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark) */ + get highWaterMark(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */ + get size(): (chunk?: any) => number; +} +interface QueuingStrategyInit { + /** + * Creates a new ByteLengthQueuingStrategy with the provided high water mark. + * + * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw. + */ + highWaterMark: number; +} +interface ScriptVersion { + id?: string; + tag?: string; + message?: string; +} +declare abstract class TailEvent extends ExtendableEvent { + readonly events: TraceItem[]; + readonly traces: TraceItem[]; +} +interface TraceItem { + readonly event: (TraceItemFetchEventInfo | TraceItemJsRpcEventInfo | TraceItemScheduledEventInfo | TraceItemAlarmEventInfo | TraceItemQueueEventInfo | TraceItemEmailEventInfo | TraceItemTailEventInfo | TraceItemCustomEventInfo | TraceItemHibernatableWebSocketEventInfo) | null; + readonly eventTimestamp: number | null; + readonly logs: TraceLog[]; + readonly exceptions: TraceException[]; + readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[]; + readonly scriptName: string | null; + readonly entrypoint?: string; + readonly scriptVersion?: ScriptVersion; + readonly dispatchNamespace?: string; + readonly scriptTags?: string[]; + readonly outcome: string; + readonly executionModel: string; + readonly truncated: boolean; + readonly cpuTime: number; + readonly wallTime: number; +} +interface TraceItemAlarmEventInfo { + readonly scheduledTime: Date; +} +interface TraceItemCustomEventInfo { +} +interface TraceItemScheduledEventInfo { + readonly scheduledTime: number; + readonly cron: string; +} +interface TraceItemQueueEventInfo { + readonly queue: string; + readonly batchSize: number; +} +interface TraceItemEmailEventInfo { + readonly mailFrom: string; + readonly rcptTo: string; + readonly rawSize: number; +} +interface TraceItemTailEventInfo { + readonly consumedEvents: TraceItemTailEventInfoTailItem[]; +} +interface TraceItemTailEventInfoTailItem { + readonly scriptName: string | null; +} +interface TraceItemFetchEventInfo { + readonly response?: TraceItemFetchEventInfoResponse; + readonly request: TraceItemFetchEventInfoRequest; +} +interface TraceItemFetchEventInfoRequest { + readonly cf?: any; + readonly headers: Record; + readonly method: string; + readonly url: string; + getUnredacted(): TraceItemFetchEventInfoRequest; +} +interface TraceItemFetchEventInfoResponse { + readonly status: number; +} +interface TraceItemJsRpcEventInfo { + readonly rpcMethod: string; +} +interface TraceItemHibernatableWebSocketEventInfo { + readonly getWebSocketEvent: TraceItemHibernatableWebSocketEventInfoMessage | TraceItemHibernatableWebSocketEventInfoClose | TraceItemHibernatableWebSocketEventInfoError; +} +interface TraceItemHibernatableWebSocketEventInfoMessage { + readonly webSocketEventType: string; +} +interface TraceItemHibernatableWebSocketEventInfoClose { + readonly webSocketEventType: string; + readonly code: number; + readonly wasClean: boolean; +} +interface TraceItemHibernatableWebSocketEventInfoError { + readonly webSocketEventType: string; +} +interface TraceLog { + readonly timestamp: number; + readonly level: string; + readonly message: any; +} +interface TraceException { + readonly timestamp: number; + readonly message: string; + readonly name: string; + readonly stack?: string; +} +interface TraceDiagnosticChannelEvent { + readonly timestamp: number; + readonly channel: string; + readonly message: any; +} +interface TraceMetrics { + readonly cpuTime: number; + readonly wallTime: number; +} +interface UnsafeTraceMetrics { + fromTrace(item: TraceItem): TraceMetrics; +} +/** + * The URL interface represents an object providing static methods used for creating object URLs. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL) + */ +declare class URL { + constructor(url: string | URL, base?: string | URL); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin) */ + get origin(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) */ + get href(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) */ + set href(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) */ + get protocol(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) */ + set protocol(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) */ + get username(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) */ + set username(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) */ + get password(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) */ + set password(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) */ + get host(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) */ + set host(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) */ + get hostname(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) */ + set hostname(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) */ + get port(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) */ + set port(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) */ + get pathname(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) */ + set pathname(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) */ + get search(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) */ + set search(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) */ + get hash(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) */ + set hash(value: string); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams) */ + get searchParams(): URLSearchParams; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON) */ + toJSON(): string; + /*function toString() { [native code] }*/ + toString(): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static) */ + static canParse(url: string, base?: string): boolean; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static) */ + static parse(url: string, base?: string): URL | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static) */ + static createObjectURL(object: File | Blob): string; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static) */ + static revokeObjectURL(object_url: string): void; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams) */ +declare class URLSearchParams { + constructor(init?: (Iterable> | Record | string)); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size) */ + get size(): number; + /** + * Appends a specified key/value pair as a new search parameter. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append) + */ + append(name: string, value: string): void; + /** + * Deletes the given search parameter, and its associated value, from the list of all search parameters. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete) + */ + delete(name: string, value?: string): void; + /** + * Returns the first value associated to the given search parameter. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get) + */ + get(name: string): string | null; + /** + * Returns all the values association with a given search parameter. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll) + */ + getAll(name: string): string[]; + /** + * Returns a Boolean indicating if such a search parameter exists. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has) + */ + has(name: string, value?: string): boolean; + /** + * Sets the value associated to a given search parameter to the given value. If there were several values, delete the others. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set) + */ + set(name: string, value: string): void; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort) */ + sort(): void; + /* Returns an array of key, value pairs for every entry in the search params. */ + entries(): IterableIterator<[ + key: string, + value: string + ]>; + /* Returns a list of keys in the search params. */ + keys(): IterableIterator; + /* Returns a list of values in the search params. */ + values(): IterableIterator; + forEach(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void; + /*function toString() { [native code] } Returns a string containing a query string suitable for use in a URL. Does not include the question mark. */ + toString(): string; + [Symbol.iterator](): IterableIterator<[ + key: string, + value: string + ]>; +} +declare class URLPattern { + constructor(input?: (string | URLPatternInit), baseURL?: (string | URLPatternOptions), patternOptions?: URLPatternOptions); + get protocol(): string; + get username(): string; + get password(): string; + get hostname(): string; + get port(): string; + get pathname(): string; + get search(): string; + get hash(): string; + get hasRegExpGroups(): boolean; + test(input?: (string | URLPatternInit), baseURL?: string): boolean; + exec(input?: (string | URLPatternInit), baseURL?: string): URLPatternResult | null; +} +interface URLPatternInit { + protocol?: string; + username?: string; + password?: string; + hostname?: string; + port?: string; + pathname?: string; + search?: string; + hash?: string; + baseURL?: string; +} +interface URLPatternComponentResult { + input: string; + groups: Record; +} +interface URLPatternResult { + inputs: (string | URLPatternInit)[]; + protocol: URLPatternComponentResult; + username: URLPatternComponentResult; + password: URLPatternComponentResult; + hostname: URLPatternComponentResult; + port: URLPatternComponentResult; + pathname: URLPatternComponentResult; + search: URLPatternComponentResult; + hash: URLPatternComponentResult; +} +interface URLPatternOptions { + ignoreCase?: boolean; +} +/** + * A CloseEvent is sent to clients using WebSockets when the connection is closed. This is delivered to the listener indicated by the WebSocket object's onclose attribute. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent) + */ +declare class CloseEvent extends Event { + constructor(type: string, initializer?: CloseEventInit); + /** + * Returns the WebSocket connection close code provided by the server. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code) + */ + readonly code: number; + /** + * Returns the WebSocket connection close reason provided by the server. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason) + */ + readonly reason: string; + /** + * Returns true if the connection closed cleanly; false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean) + */ + readonly wasClean: boolean; +} +interface CloseEventInit { + code?: number; + reason?: string; + wasClean?: boolean; +} +/** + * A message received by a target object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent) + */ +declare class MessageEvent extends Event { + constructor(type: string, initializer: MessageEventInit); + /** + * Returns the data of the message. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data) + */ + readonly data: ArrayBuffer | string; +} +interface MessageEventInit { + data: ArrayBuffer | string; +} +type WebSocketEventMap = { + close: CloseEvent; + message: MessageEvent; + open: Event; + error: ErrorEvent; +}; +/** + * Provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) + */ +declare var WebSocket: { + prototype: WebSocket; + new (url: string, protocols?: (string[] | string)): WebSocket; + readonly READY_STATE_CONNECTING: number; + readonly CONNECTING: number; + readonly READY_STATE_OPEN: number; + readonly OPEN: number; + readonly READY_STATE_CLOSING: number; + readonly CLOSING: number; + readonly READY_STATE_CLOSED: number; + readonly CLOSED: number; +}; +/** + * Provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) + */ +interface WebSocket extends EventTarget { + accept(): void; + /** + * Transmits data using the WebSocket connection. data can be a string, a Blob, an ArrayBuffer, or an ArrayBufferView. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send) + */ + send(message: (ArrayBuffer | ArrayBufferView) | string): void; + /** + * Closes the WebSocket connection, optionally using code as the the WebSocket connection close code and reason as the the WebSocket connection close reason. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close) + */ + close(code?: number, reason?: string): void; + serializeAttachment(attachment: any): void; + deserializeAttachment(): any | null; + /** + * Returns the state of the WebSocket object's connection. It can have the values described below. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState) + */ + readyState: number; + /** + * Returns the URL that was used to establish the WebSocket connection. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url) + */ + url: string | null; + /** + * Returns the subprotocol selected by the server, if any. It can be used in conjunction with the array form of the constructor's second argument to perform subprotocol negotiation. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol) + */ + protocol: string | null; + /** + * Returns the extensions selected by the server, if any. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions) + */ + extensions: string | null; +} +declare const WebSocketPair: { + new (): { + 0: WebSocket; + 1: WebSocket; + }; +}; +interface SqlStorage { + exec>(query: string, ...bindings: any[]): SqlStorageCursor; + get databaseSize(): number; + Cursor: typeof SqlStorageCursor; + Statement: typeof SqlStorageStatement; +} +declare abstract class SqlStorageStatement { +} +type SqlStorageValue = ArrayBuffer | string | number | null; +declare abstract class SqlStorageCursor> { + next(): { + done?: false; + value: T; + } | { + done: true; + value?: never; + }; + toArray(): T[]; + one(): T; + raw(): IterableIterator; + columnNames: string[]; + get rowsRead(): number; + get rowsWritten(): number; + [Symbol.iterator](): IterableIterator; +} +interface Socket { + get readable(): ReadableStream; + get writable(): WritableStream; + get closed(): Promise; + get opened(): Promise; + get upgraded(): boolean; + get secureTransport(): "on" | "off" | "starttls"; + close(): Promise; + startTls(options?: TlsOptions): Socket; +} +interface SocketOptions { + secureTransport?: string; + allowHalfOpen: boolean; + highWaterMark?: (number | bigint); +} +interface SocketAddress { + hostname: string; + port: number; +} +interface TlsOptions { + expectedServerHostname?: string; +} +interface SocketInfo { + remoteAddress?: string; + localAddress?: string; +} +/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource) */ +declare class EventSource extends EventTarget { + constructor(url: string, init?: EventSourceEventSourceInit); + /** + * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close) + */ + close(): void; + /** + * Returns the URL providing the event stream. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url) + */ + get url(): string; + /** + * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials) + */ + get withCredentials(): boolean; + /** + * Returns the state of this EventSource object's connection. It can have the values described below. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState) + */ + get readyState(): number; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ + get onopen(): any | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ + set onopen(value: any | null); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ + get onmessage(): any | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ + set onmessage(value: any | null); + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ + get onerror(): any | null; + /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ + set onerror(value: any | null); + static readonly CONNECTING: number; + static readonly OPEN: number; + static readonly CLOSED: number; + static from(stream: ReadableStream): EventSource; +} +interface EventSourceEventSourceInit { + withCredentials?: boolean; + fetcher?: Fetcher; +} +interface Container { + get running(): boolean; + start(options?: ContainerStartupOptions): void; + monitor(): Promise; + destroy(error?: any): Promise; + signal(signo: number): void; + getTcpPort(port: number): Fetcher; +} +interface ContainerStartupOptions { + entrypoint?: string[]; + enableInternet: boolean; + env?: Record; +} +type AiImageClassificationInput = { + image: number[]; +}; +type AiImageClassificationOutput = { + score?: number; + label?: string; +}[]; +declare abstract class BaseAiImageClassification { + inputs: AiImageClassificationInput; + postProcessedOutputs: AiImageClassificationOutput; +} +type AiImageToTextInput = { + image: number[]; + prompt?: string; + max_tokens?: number; + temperature?: number; + top_p?: number; + top_k?: number; + seed?: number; + repetition_penalty?: number; + frequency_penalty?: number; + presence_penalty?: number; + raw?: boolean; + messages?: RoleScopedChatInput[]; +}; +type AiImageToTextOutput = { + description: string; +}; +declare abstract class BaseAiImageToText { + inputs: AiImageToTextInput; + postProcessedOutputs: AiImageToTextOutput; +} +type AiImageTextToTextInput = { + image: string; + prompt?: string; + max_tokens?: number; + temperature?: number; + ignore_eos?: boolean; + top_p?: number; + top_k?: number; + seed?: number; + repetition_penalty?: number; + frequency_penalty?: number; + presence_penalty?: number; + raw?: boolean; + messages?: RoleScopedChatInput[]; +}; +type AiImageTextToTextOutput = { + description: string; +}; +declare abstract class BaseAiImageTextToText { + inputs: AiImageTextToTextInput; + postProcessedOutputs: AiImageTextToTextOutput; +} +type AiObjectDetectionInput = { + image: number[]; +}; +type AiObjectDetectionOutput = { + score?: number; + label?: string; +}[]; +declare abstract class BaseAiObjectDetection { + inputs: AiObjectDetectionInput; + postProcessedOutputs: AiObjectDetectionOutput; +} +type AiSentenceSimilarityInput = { + source: string; + sentences: string[]; +}; +type AiSentenceSimilarityOutput = number[]; +declare abstract class BaseAiSentenceSimilarity { + inputs: AiSentenceSimilarityInput; + postProcessedOutputs: AiSentenceSimilarityOutput; +} +type AiAutomaticSpeechRecognitionInput = { + audio: number[]; +}; +type AiAutomaticSpeechRecognitionOutput = { + text?: string; + words?: { + word: string; + start: number; + end: number; + }[]; + vtt?: string; +}; +declare abstract class BaseAiAutomaticSpeechRecognition { + inputs: AiAutomaticSpeechRecognitionInput; + postProcessedOutputs: AiAutomaticSpeechRecognitionOutput; +} +type AiSummarizationInput = { + input_text: string; + max_length?: number; +}; +type AiSummarizationOutput = { + summary: string; +}; +declare abstract class BaseAiSummarization { + inputs: AiSummarizationInput; + postProcessedOutputs: AiSummarizationOutput; +} +type AiTextClassificationInput = { + text: string; +}; +type AiTextClassificationOutput = { + score?: number; + label?: string; +}[]; +declare abstract class BaseAiTextClassification { + inputs: AiTextClassificationInput; + postProcessedOutputs: AiTextClassificationOutput; +} +type AiTextEmbeddingsInput = { + text: string | string[]; +}; +type AiTextEmbeddingsOutput = { + shape: number[]; + data: number[][]; +}; +declare abstract class BaseAiTextEmbeddings { + inputs: AiTextEmbeddingsInput; + postProcessedOutputs: AiTextEmbeddingsOutput; +} +type RoleScopedChatInput = { + role: "user" | "assistant" | "system" | "tool" | (string & NonNullable); + content: string; + name?: string; +}; +type AiTextGenerationToolLegacyInput = { + name: string; + description: string; + parameters?: { + type: "object" | (string & NonNullable); + properties: { + [key: string]: { + type: string; + description?: string; + }; + }; + required: string[]; + }; +}; +type AiTextGenerationToolInput = { + type: "function" | (string & NonNullable); + function: { + name: string; + description: string; + parameters?: { + type: "object" | (string & NonNullable); + properties: { + [key: string]: { + type: string; + description?: string; + }; + }; + required: string[]; + }; + }; +}; +type AiTextGenerationFunctionsInput = { + name: string; + code: string; +}; +type AiTextGenerationResponseFormat = { + type: string; + json_schema?: any; +}; +type AiTextGenerationInput = { + prompt?: string; + raw?: boolean; + stream?: boolean; + max_tokens?: number; + temperature?: number; + top_p?: number; + top_k?: number; + seed?: number; + repetition_penalty?: number; + frequency_penalty?: number; + presence_penalty?: number; + messages?: RoleScopedChatInput[]; + response_format?: AiTextGenerationResponseFormat; + tools?: AiTextGenerationToolInput[] | AiTextGenerationToolLegacyInput[] | (object & NonNullable); + functions?: AiTextGenerationFunctionsInput[]; +}; +type AiTextGenerationOutput = { + response?: string; + tool_calls?: { + name: string; + arguments: unknown; + }[]; +}; +declare abstract class BaseAiTextGeneration { + inputs: AiTextGenerationInput; + postProcessedOutputs: AiTextGenerationOutput; +} +type AiTextToSpeechInput = { + prompt: string; + lang?: string; +}; +type AiTextToSpeechOutput = Uint8Array | { + audio: string; +}; +declare abstract class BaseAiTextToSpeech { + inputs: AiTextToSpeechInput; + postProcessedOutputs: AiTextToSpeechOutput; +} +type AiTextToImageInput = { + prompt: string; + negative_prompt?: string; + height?: number; + width?: number; + image?: number[]; + image_b64?: string; + mask?: number[]; + num_steps?: number; + strength?: number; + guidance?: number; + seed?: number; +}; +type AiTextToImageOutput = ReadableStream; +declare abstract class BaseAiTextToImage { + inputs: AiTextToImageInput; + postProcessedOutputs: AiTextToImageOutput; +} +type AiTranslationInput = { + text: string; + target_lang: string; + source_lang?: string; +}; +type AiTranslationOutput = { + translated_text?: string; +}; +declare abstract class BaseAiTranslation { + inputs: AiTranslationInput; + postProcessedOutputs: AiTranslationOutput; +} +type Ai_Cf_Baai_Bge_Base_En_V1_5_Input = { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; + }[]; +}; +type Ai_Cf_Baai_Bge_Base_En_V1_5_Output = { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} | AsyncResponse; +interface AsyncResponse { + /** + * The async request id that can be used to obtain the results. + */ + request_id?: string; +} +declare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 { + inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input; + postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output; +} +type Ai_Cf_Openai_Whisper_Input = string | { + /** + * An array of integers that represent the audio data constrained to 8-bit unsigned integer values + */ + audio: number[]; +}; +interface Ai_Cf_Openai_Whisper_Output { + /** + * The transcription + */ + text: string; + word_count?: number; + words?: { + word?: string; + /** + * The second this word begins in the recording + */ + start?: number; + /** + * The ending second when the word completes + */ + end?: number; + }[]; + vtt?: string; +} +declare abstract class Base_Ai_Cf_Openai_Whisper { + inputs: Ai_Cf_Openai_Whisper_Input; + postProcessedOutputs: Ai_Cf_Openai_Whisper_Output; +} +type Ai_Cf_Meta_M2M100_1_2B_Input = { + /** + * The text to be translated + */ + text: string; + /** + * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified + */ + source_lang?: string; + /** + * The language code to translate the text into (e.g., 'es' for Spanish) + */ + target_lang: string; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + /** + * The text to be translated + */ + text: string; + /** + * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified + */ + source_lang?: string; + /** + * The language code to translate the text into (e.g., 'es' for Spanish) + */ + target_lang: string; + }[]; +}; +type Ai_Cf_Meta_M2M100_1_2B_Output = { + /** + * The translated text in the target language + */ + translated_text?: string; +} | AsyncResponse; +declare abstract class Base_Ai_Cf_Meta_M2M100_1_2B { + inputs: Ai_Cf_Meta_M2M100_1_2B_Input; + postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output; +} +type Ai_Cf_Baai_Bge_Small_En_V1_5_Input = { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; + }[]; +}; +type Ai_Cf_Baai_Bge_Small_En_V1_5_Output = { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} | AsyncResponse; +declare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 { + inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input; + postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output; +} +type Ai_Cf_Baai_Bge_Large_En_V1_5_Input = { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; +} | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: { + text: string | string[]; + /** + * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. + */ + pooling?: "mean" | "cls"; + }[]; +}; +type Ai_Cf_Baai_Bge_Large_En_V1_5_Output = { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} | AsyncResponse; +declare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 { + inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input; + postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output; +} +type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input = string | { + /** + * The input text prompt for the model to generate a response. + */ + prompt?: string; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; + image: number[] | (string & NonNullable); + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; +}; +interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output { + description?: string; +} +declare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M { + inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input; + postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output; +} +type Ai_Cf_Openai_Whisper_Tiny_En_Input = string | { + /** + * An array of integers that represent the audio data constrained to 8-bit unsigned integer values + */ + audio: number[]; +}; +interface Ai_Cf_Openai_Whisper_Tiny_En_Output { + /** + * The transcription + */ + text: string; + word_count?: number; + words?: { + word?: string; + /** + * The second this word begins in the recording + */ + start?: number; + /** + * The ending second when the word completes + */ + end?: number; + }[]; + vtt?: string; +} +declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En { + inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input; + postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output; +} +interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { + /** + * Base64 encoded value of the audio data. + */ + audio: string; + /** + * Supported tasks are 'translate' or 'transcribe'. + */ + task?: string; + /** + * The language of the audio being transcribed or translated. + */ + language?: string; + /** + * Preprocess the audio with a voice activity detection model. + */ + vad_filter?: boolean; + /** + * A text prompt to help provide context to the model on the contents of the audio. + */ + initial_prompt?: string; + /** + * The prefix it appended the the beginning of the output of the transcription and can guide the transcription result. + */ + prefix?: string; +} +interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output { + transcription_info?: { + /** + * The language of the audio being transcribed or translated. + */ + language?: string; + /** + * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1. + */ + language_probability?: number; + /** + * The total duration of the original audio file, in seconds. + */ + duration?: number; + /** + * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds. + */ + duration_after_vad?: number; + }; + /** + * The complete transcription of the audio. + */ + text: string; + /** + * The total number of words in the transcription. + */ + word_count?: number; + segments?: { + /** + * The starting time of the segment within the audio, in seconds. + */ + start?: number; + /** + * The ending time of the segment within the audio, in seconds. + */ + end?: number; + /** + * The transcription of the segment. + */ + text?: string; + /** + * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs. + */ + temperature?: number; + /** + * The average log probability of the predictions for the words in this segment, indicating overall confidence. + */ + avg_logprob?: number; + /** + * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process. + */ + compression_ratio?: number; + /** + * The probability that the segment contains no speech, represented as a decimal between 0 and 1. + */ + no_speech_prob?: number; + words?: { + /** + * The individual word transcribed from the audio. + */ + word?: string; + /** + * The starting time of the word within the audio, in seconds. + */ + start?: number; + /** + * The ending time of the word within the audio, in seconds. + */ + end?: number; + }[]; + }[]; + /** + * The transcription in WebVTT format, which includes timing and text information for use in subtitles. + */ + vtt?: string; +} +declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo { + inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input; + postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output; +} +type Ai_Cf_Baai_Bge_M3_Input = BGEM3InputQueryAndContexts | BGEM3InputEmbedding | { + /** + * Batch of the embeddings requests to run using async-queue + */ + requests: (BGEM3InputQueryAndContexts1 | BGEM3InputEmbedding1)[]; +}; +interface BGEM3InputQueryAndContexts { + /** + * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts + */ + query?: string; + /** + * List of provided contexts. Note that the index in this array is important, as the response will refer to it. + */ + contexts: { + /** + * One of the provided context content + */ + text?: string; + }[]; + /** + * When provided with too long context should the model error out or truncate the context to fit? + */ + truncate_inputs?: boolean; +} +interface BGEM3InputEmbedding { + text: string | string[]; + /** + * When provided with too long context should the model error out or truncate the context to fit? + */ + truncate_inputs?: boolean; +} +interface BGEM3InputQueryAndContexts1 { + /** + * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts + */ + query?: string; + /** + * List of provided contexts. Note that the index in this array is important, as the response will refer to it. + */ + contexts: { + /** + * One of the provided context content + */ + text?: string; + }[]; + /** + * When provided with too long context should the model error out or truncate the context to fit? + */ + truncate_inputs?: boolean; +} +interface BGEM3InputEmbedding1 { + text: string | string[]; + /** + * When provided with too long context should the model error out or truncate the context to fit? + */ + truncate_inputs?: boolean; +} +type Ai_Cf_Baai_Bge_M3_Output = BGEM3OuputQuery | BGEM3OutputEmbeddingForContexts | BGEM3OuputEmbedding | AsyncResponse; +interface BGEM3OuputQuery { + response?: { + /** + * Index of the context in the request + */ + id?: number; + /** + * Score of the context under the index. + */ + score?: number; + }[]; +} +interface BGEM3OutputEmbeddingForContexts { + response?: number[][]; + shape?: number[]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} +interface BGEM3OuputEmbedding { + shape?: number[]; + /** + * Embeddings of the requested text values + */ + data?: number[][]; + /** + * The pooling method used in the embedding process. + */ + pooling?: "mean" | "cls"; +} +declare abstract class Base_Ai_Cf_Baai_Bge_M3 { + inputs: Ai_Cf_Baai_Bge_M3_Input; + postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output; +} +interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input { + /** + * A text description of the image you want to generate. + */ + prompt: string; + /** + * The number of diffusion steps; higher values can improve quality but take longer. + */ + steps?: number; +} +interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output { + /** + * The generated image in Base64 format. + */ + image?: string; +} +declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell { + inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input; + postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output; +} +type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = Prompt | Messages; +interface Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + image?: number[] | (string & NonNullable); + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; + /** + * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. + */ + lora?: string; +} +interface Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role?: string; + /** + * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 + */ + tool_call_id?: string; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; + }[]; + image?: number[] | (string & NonNullable); + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + /** + * If true, the response will be streamed back incrementally. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = { + /** + * The generated text response from the model + */ + response?: string; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +}; +declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct { + inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input; + postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output; +} +type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input = Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt | Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages | AsyncBatch; +interface Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. + */ + lora?: string; + response_format?: JSONMode; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface JSONMode { + type?: "json_object" | "json_schema"; + json_schema?: unknown; +} +interface Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role: string; + /** + * The content of the message as a string. + */ + content: string; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + response_format?: JSONMode; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface AsyncBatch { + requests?: { + /** + * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique. + */ + external_reference?: string; + /** + * Prompt for the text generation model + */ + prompt?: string; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; + response_format?: JSONMode; + }[]; +} +type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +} | AsyncResponse; +declare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast { + inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input; + postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output; +} +interface Ai_Cf_Meta_Llama_Guard_3_8B_Input { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender must alternate between 'user' and 'assistant'. + */ + role: "user" | "assistant"; + /** + * The content of the message as a string. + */ + content: string; + }[]; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Dictate the output format of the generated response. + */ + response_format?: { + /** + * Set to json_object to process and output generated text as JSON. + */ + type?: string; + }; +} +interface Ai_Cf_Meta_Llama_Guard_3_8B_Output { + response?: string | { + /** + * Whether the conversation is safe or not. + */ + safe?: boolean; + /** + * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe. + */ + categories?: string[]; + }; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; +} +declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B { + inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input; + postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output; +} +interface Ai_Cf_Baai_Bge_Reranker_Base_Input { + /** + * A query you wish to perform against the provided contexts. + */ + query: string; + /** + * Number of returned results starting with the best score. + */ + top_k?: number; + /** + * List of provided contexts. Note that the index in this array is important, as the response will refer to it. + */ + contexts: { + /** + * One of the provided context content + */ + text?: string; + }[]; +} +interface Ai_Cf_Baai_Bge_Reranker_Base_Output { + response?: { + /** + * Index of the context in the request + */ + id?: number; + /** + * Score of the context under the index. + */ + score?: number; + }[]; +} +declare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base { + inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input; + postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output; +} +type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input = Qwen2_5_Coder_32B_Instruct_Prompt | Qwen2_5_Coder_32B_Instruct_Messages; +interface Qwen2_5_Coder_32B_Instruct_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. + */ + lora?: string; + response_format?: JSONMode; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface Qwen2_5_Coder_32B_Instruct_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role: string; + /** + * The content of the message as a string. + */ + content: string; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + response_format?: JSONMode; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +}; +declare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct { + inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input; + postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output; +} +type Ai_Cf_Qwen_Qwq_32B_Input = Qwen_Qwq_32B_Prompt | Qwen_Qwq_32B_Messages; +interface Qwen_Qwq_32B_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * JSON schema that should be fulfilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface Qwen_Qwq_32B_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role?: string; + /** + * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 + */ + tool_call_id?: string; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + /** + * JSON schema that should be fufilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Qwen_Qwq_32B_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +}; +declare abstract class Base_Ai_Cf_Qwen_Qwq_32B { + inputs: Ai_Cf_Qwen_Qwq_32B_Input; + postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output; +} +type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input = Mistral_Small_3_1_24B_Instruct_Prompt | Mistral_Small_3_1_24B_Instruct_Messages; +interface Mistral_Small_3_1_24B_Instruct_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * JSON schema that should be fulfilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface Mistral_Small_3_1_24B_Instruct_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role?: string; + /** + * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 + */ + tool_call_id?: string; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + /** + * JSON schema that should be fufilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +}; +declare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct { + inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input; + postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output; +} +type Ai_Cf_Google_Gemma_3_12B_It_Input = Google_Gemma_3_12B_It_Prompt | Google_Gemma_3_12B_It_Messages; +interface Google_Gemma_3_12B_It_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * JSON schema that should be fufilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface Google_Gemma_3_12B_It_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role?: string; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + /** + * JSON schema that should be fufilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Google_Gemma_3_12B_It_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + /** + * The name of the tool to be called + */ + name?: string; + }[]; +}; +declare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It { + inputs: Ai_Cf_Google_Gemma_3_12B_It_Input; + postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output; +} +type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input = Ai_Cf_Meta_Llama_4_Prompt | Ai_Cf_Meta_Llama_4_Messages; +interface Ai_Cf_Meta_Llama_4_Prompt { + /** + * The input text prompt for the model to generate a response. + */ + prompt: string; + /** + * JSON schema that should be fulfilled for the response. + */ + guided_json?: object; + response_format?: JSONMode; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +interface Ai_Cf_Meta_Llama_4_Messages { + /** + * An array of message objects representing the conversation history. + */ + messages: { + /** + * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). + */ + role?: string; + /** + * The tool call id. If you don't know what to put here you can fall back to 000000001 + */ + tool_call_id?: string; + content?: string | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }[] | { + /** + * Type of the content provided + */ + type?: string; + text?: string; + image_url?: { + /** + * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted + */ + url?: string; + }; + }; + }[]; + functions?: { + name: string; + code: string; + }[]; + /** + * A list of tools available for the assistant to use. + */ + tools?: ({ + /** + * The name of the tool. More descriptive the better. + */ + name: string; + /** + * A brief description of what the tool does. + */ + description: string; + /** + * Schema defining the parameters accepted by the tool. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + } | { + /** + * Specifies the type of tool (e.g., 'function'). + */ + type: string; + /** + * Details of the function tool. + */ + function: { + /** + * The name of the function. + */ + name: string; + /** + * A brief description of what the function does. + */ + description: string; + /** + * Schema defining the parameters accepted by the function. + */ + parameters: { + /** + * The type of the parameters object (usually 'object'). + */ + type: string; + /** + * List of required parameter names. + */ + required?: string[]; + /** + * Definitions of each parameter. + */ + properties: { + [k: string]: { + /** + * The data type of the parameter. + */ + type: string; + /** + * A description of the expected parameter. + */ + description: string; + }; + }; + }; + }; + })[]; + response_format?: JSONMode; + /** + * JSON schema that should be fufilled for the response. + */ + guided_json?: object; + /** + * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. + */ + raw?: boolean; + /** + * If true, the response will be streamed back incrementally using SSE, Server Sent Events. + */ + stream?: boolean; + /** + * The maximum number of tokens to generate in the response. + */ + max_tokens?: number; + /** + * Controls the randomness of the output; higher values produce more random results. + */ + temperature?: number; + /** + * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. + */ + top_p?: number; + /** + * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. + */ + top_k?: number; + /** + * Random seed for reproducibility of the generation. + */ + seed?: number; + /** + * Penalty for repeated tokens; higher values discourage repetition. + */ + repetition_penalty?: number; + /** + * Decreases the likelihood of the model repeating the same lines verbatim. + */ + frequency_penalty?: number; + /** + * Increases the likelihood of the model introducing new topics. + */ + presence_penalty?: number; +} +type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = { + /** + * The generated text response from the model + */ + response: string; + /** + * Usage statistics for the inference request + */ + usage?: { + /** + * Total number of tokens in input + */ + prompt_tokens?: number; + /** + * Total number of tokens in output + */ + completion_tokens?: number; + /** + * Total number of input and output tokens + */ + total_tokens?: number; + }; + /** + * An array of tool calls requests made during the response generation + */ + tool_calls?: { + /** + * The tool call id. + */ + id?: string; + /** + * Specifies the type of tool (e.g., 'function'). + */ + type?: string; + /** + * Details of the function tool. + */ + function?: { + /** + * The name of the tool to be called + */ + name?: string; + /** + * The arguments passed to be passed to the tool call request + */ + arguments?: object; + }; + }[]; +}; +declare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct { + inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input; + postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output; +} +interface AiModels { + "@cf/huggingface/distilbert-sst-2-int8": BaseAiTextClassification; + "@cf/stabilityai/stable-diffusion-xl-base-1.0": BaseAiTextToImage; + "@cf/runwayml/stable-diffusion-v1-5-inpainting": BaseAiTextToImage; + "@cf/runwayml/stable-diffusion-v1-5-img2img": BaseAiTextToImage; + "@cf/lykon/dreamshaper-8-lcm": BaseAiTextToImage; + "@cf/bytedance/stable-diffusion-xl-lightning": BaseAiTextToImage; + "@cf/myshell-ai/melotts": BaseAiTextToSpeech; + "@cf/microsoft/resnet-50": BaseAiImageClassification; + "@cf/facebook/detr-resnet-50": BaseAiObjectDetection; + "@cf/meta/llama-2-7b-chat-int8": BaseAiTextGeneration; + "@cf/mistral/mistral-7b-instruct-v0.1": BaseAiTextGeneration; + "@cf/meta/llama-2-7b-chat-fp16": BaseAiTextGeneration; + "@hf/thebloke/llama-2-13b-chat-awq": BaseAiTextGeneration; + "@hf/thebloke/mistral-7b-instruct-v0.1-awq": BaseAiTextGeneration; + "@hf/thebloke/zephyr-7b-beta-awq": BaseAiTextGeneration; + "@hf/thebloke/openhermes-2.5-mistral-7b-awq": BaseAiTextGeneration; + "@hf/thebloke/neural-chat-7b-v3-1-awq": BaseAiTextGeneration; + "@hf/thebloke/llamaguard-7b-awq": BaseAiTextGeneration; + "@hf/thebloke/deepseek-coder-6.7b-base-awq": BaseAiTextGeneration; + "@hf/thebloke/deepseek-coder-6.7b-instruct-awq": BaseAiTextGeneration; + "@cf/deepseek-ai/deepseek-math-7b-instruct": BaseAiTextGeneration; + "@cf/defog/sqlcoder-7b-2": BaseAiTextGeneration; + "@cf/openchat/openchat-3.5-0106": BaseAiTextGeneration; + "@cf/tiiuae/falcon-7b-instruct": BaseAiTextGeneration; + "@cf/thebloke/discolm-german-7b-v1-awq": BaseAiTextGeneration; + "@cf/qwen/qwen1.5-0.5b-chat": BaseAiTextGeneration; + "@cf/qwen/qwen1.5-7b-chat-awq": BaseAiTextGeneration; + "@cf/qwen/qwen1.5-14b-chat-awq": BaseAiTextGeneration; + "@cf/tinyllama/tinyllama-1.1b-chat-v1.0": BaseAiTextGeneration; + "@cf/microsoft/phi-2": BaseAiTextGeneration; + "@cf/qwen/qwen1.5-1.8b-chat": BaseAiTextGeneration; + "@cf/mistral/mistral-7b-instruct-v0.2-lora": BaseAiTextGeneration; + "@hf/nousresearch/hermes-2-pro-mistral-7b": BaseAiTextGeneration; + "@hf/nexusflow/starling-lm-7b-beta": BaseAiTextGeneration; + "@hf/google/gemma-7b-it": BaseAiTextGeneration; + "@cf/meta-llama/llama-2-7b-chat-hf-lora": BaseAiTextGeneration; + "@cf/google/gemma-2b-it-lora": BaseAiTextGeneration; + "@cf/google/gemma-7b-it-lora": BaseAiTextGeneration; + "@hf/mistral/mistral-7b-instruct-v0.2": BaseAiTextGeneration; + "@cf/meta/llama-3-8b-instruct": BaseAiTextGeneration; + "@cf/fblgit/una-cybertron-7b-v2-bf16": BaseAiTextGeneration; + "@cf/meta/llama-3-8b-instruct-awq": BaseAiTextGeneration; + "@hf/meta-llama/meta-llama-3-8b-instruct": BaseAiTextGeneration; + "@cf/meta/llama-3.1-8b-instruct": BaseAiTextGeneration; + "@cf/meta/llama-3.1-8b-instruct-fp8": BaseAiTextGeneration; + "@cf/meta/llama-3.1-8b-instruct-awq": BaseAiTextGeneration; + "@cf/meta/llama-3.2-3b-instruct": BaseAiTextGeneration; + "@cf/meta/llama-3.2-1b-instruct": BaseAiTextGeneration; + "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": BaseAiTextGeneration; + "@cf/facebook/bart-large-cnn": BaseAiSummarization; + "@cf/llava-hf/llava-1.5-7b-hf": BaseAiImageToText; + "@cf/baai/bge-base-en-v1.5": Base_Ai_Cf_Baai_Bge_Base_En_V1_5; + "@cf/openai/whisper": Base_Ai_Cf_Openai_Whisper; + "@cf/meta/m2m100-1.2b": Base_Ai_Cf_Meta_M2M100_1_2B; + "@cf/baai/bge-small-en-v1.5": Base_Ai_Cf_Baai_Bge_Small_En_V1_5; + "@cf/baai/bge-large-en-v1.5": Base_Ai_Cf_Baai_Bge_Large_En_V1_5; + "@cf/unum/uform-gen2-qwen-500m": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M; + "@cf/openai/whisper-tiny-en": Base_Ai_Cf_Openai_Whisper_Tiny_En; + "@cf/openai/whisper-large-v3-turbo": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo; + "@cf/baai/bge-m3": Base_Ai_Cf_Baai_Bge_M3; + "@cf/black-forest-labs/flux-1-schnell": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell; + "@cf/meta/llama-3.2-11b-vision-instruct": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct; + "@cf/meta/llama-3.3-70b-instruct-fp8-fast": Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast; + "@cf/meta/llama-guard-3-8b": Base_Ai_Cf_Meta_Llama_Guard_3_8B; + "@cf/baai/bge-reranker-base": Base_Ai_Cf_Baai_Bge_Reranker_Base; + "@cf/qwen/qwen2.5-coder-32b-instruct": Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct; + "@cf/qwen/qwq-32b": Base_Ai_Cf_Qwen_Qwq_32B; + "@cf/mistralai/mistral-small-3.1-24b-instruct": Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct; + "@cf/google/gemma-3-12b-it": Base_Ai_Cf_Google_Gemma_3_12B_It; + "@cf/meta/llama-4-scout-17b-16e-instruct": Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct; +} +type AiOptions = { + /** + * Send requests as an asynchronous batch job, only works for supported models + * https://developers.cloudflare.com/workers-ai/features/batch-api + */ + queueRequest?: boolean; + gateway?: GatewayOptions; + returnRawResponse?: boolean; + prefix?: string; + extraHeaders?: object; +}; +type ConversionResponse = { + name: string; + mimeType: string; + format: "markdown"; + tokens: number; + data: string; +}; +type AiModelsSearchParams = { + author?: string; + hide_experimental?: boolean; + page?: number; + per_page?: number; + search?: string; + source?: number; + task?: string; +}; +type AiModelsSearchObject = { + id: string; + source: number; + name: string; + description: string; + task: { + id: string; + name: string; + description: string; + }; + tags: string[]; + properties: { + property_id: string; + value: string; + }[]; +}; +interface InferenceUpstreamError extends Error { +} +interface AiInternalError extends Error { +} +type AiModelListType = Record; +declare abstract class Ai { + aiGatewayLogId: string | null; + gateway(gatewayId: string): AiGateway; + autorag(autoragId?: string): AutoRAG; + run(model: Name, inputs: InputOptions, options?: Options): Promise; + models(params?: AiModelsSearchParams): Promise; + toMarkdown(files: { + name: string; + blob: Blob; + }[], options?: { + gateway?: GatewayOptions; + extraHeaders?: object; + }): Promise; + toMarkdown(files: { + name: string; + blob: Blob; + }, options?: { + gateway?: GatewayOptions; + extraHeaders?: object; + }): Promise; +} +type GatewayRetries = { + maxAttempts?: 1 | 2 | 3 | 4 | 5; + retryDelayMs?: number; + backoff?: 'constant' | 'linear' | 'exponential'; +}; +type GatewayOptions = { + id: string; + cacheKey?: string; + cacheTtl?: number; + skipCache?: boolean; + metadata?: Record; + collectLog?: boolean; + eventId?: string; + requestTimeoutMs?: number; + retries?: GatewayRetries; +}; +type AiGatewayPatchLog = { + score?: number | null; + feedback?: -1 | 1 | null; + metadata?: Record | null; +}; +type AiGatewayLog = { + id: string; + provider: string; + model: string; + model_type?: string; + path: string; + duration: number; + request_type?: string; + request_content_type?: string; + status_code: number; + response_content_type?: string; + success: boolean; + cached: boolean; + tokens_in?: number; + tokens_out?: number; + metadata?: Record; + step?: number; + cost?: number; + custom_cost?: boolean; + request_size: number; + request_head?: string; + request_head_complete: boolean; + response_size: number; + response_head?: string; + response_head_complete: boolean; + created_at: Date; +}; +type AIGatewayProviders = 'workers-ai' | 'anthropic' | 'aws-bedrock' | 'azure-openai' | 'google-vertex-ai' | 'huggingface' | 'openai' | 'perplexity-ai' | 'replicate' | 'groq' | 'cohere' | 'google-ai-studio' | 'mistral' | 'grok' | 'openrouter' | 'deepseek' | 'cerebras' | 'cartesia' | 'elevenlabs' | 'adobe-firefly'; +type AIGatewayHeaders = { + 'cf-aig-metadata': Record | string; + 'cf-aig-custom-cost': { + per_token_in?: number; + per_token_out?: number; + } | { + total_cost?: number; + } | string; + 'cf-aig-cache-ttl': number | string; + 'cf-aig-skip-cache': boolean | string; + 'cf-aig-cache-key': string; + 'cf-aig-event-id': string; + 'cf-aig-request-timeout': number | string; + 'cf-aig-max-attempts': number | string; + 'cf-aig-retry-delay': number | string; + 'cf-aig-backoff': string; + 'cf-aig-collect-log': boolean | string; + Authorization: string; + 'Content-Type': string; + [key: string]: string | number | boolean | object; +}; +type AIGatewayUniversalRequest = { + provider: AIGatewayProviders | string; // eslint-disable-line + endpoint: string; + headers: Partial; + query: unknown; +}; +interface AiGatewayInternalError extends Error { +} +interface AiGatewayLogNotFound extends Error { +} +declare abstract class AiGateway { + patchLog(logId: string, data: AiGatewayPatchLog): Promise; + getLog(logId: string): Promise; + run(data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[], options?: { + gateway?: GatewayOptions; + extraHeaders?: object; + }): Promise; + getUrl(provider?: AIGatewayProviders | string): Promise; // eslint-disable-line +} +interface AutoRAGInternalError extends Error { +} +interface AutoRAGNotFoundError extends Error { +} +interface AutoRAGUnauthorizedError extends Error { +} +interface AutoRAGNameNotSetError extends Error { +} +type ComparisonFilter = { + key: string; + type: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'; + value: string | number | boolean; +}; +type CompoundFilter = { + type: 'and' | 'or'; + filters: ComparisonFilter[]; +}; +type AutoRagSearchRequest = { + query: string; + filters?: CompoundFilter | ComparisonFilter; + max_num_results?: number; + ranking_options?: { + ranker?: string; + score_threshold?: number; + }; + rewrite_query?: boolean; +}; +type AutoRagAiSearchRequest = AutoRagSearchRequest & { + stream?: boolean; +}; +type AutoRagAiSearchRequestStreaming = Omit & { + stream: true; +}; +type AutoRagSearchResponse = { + object: 'vector_store.search_results.page'; + search_query: string; + data: { + file_id: string; + filename: string; + score: number; + attributes: Record; + content: { + type: 'text'; + text: string; + }[]; + }[]; + has_more: boolean; + next_page: string | null; +}; +type AutoRagListResponse = { + id: string; + enable: boolean; + type: string; + source: string; + vectorize_name: string; + paused: boolean; + status: string; +}[]; +type AutoRagAiSearchResponse = AutoRagSearchResponse & { + response: string; +}; +declare abstract class AutoRAG { + list(): Promise; + search(params: AutoRagSearchRequest): Promise; + aiSearch(params: AutoRagAiSearchRequestStreaming): Promise; + aiSearch(params: AutoRagAiSearchRequest): Promise; + aiSearch(params: AutoRagAiSearchRequest): Promise; +} +interface BasicImageTransformations { + /** + * Maximum width in image pixels. The value must be an integer. + */ + width?: number; + /** + * Maximum height in image pixels. The value must be an integer. + */ + height?: number; + /** + * Resizing mode as a string. It affects interpretation of width and height + * options: + * - scale-down: Similar to contain, but the image is never enlarged. If + * the image is larger than given width or height, it will be resized. + * Otherwise its original size will be kept. + * - contain: Resizes to maximum size that fits within the given width and + * height. If only a single dimension is given (e.g. only width), the + * image will be shrunk or enlarged to exactly match that dimension. + * Aspect ratio is always preserved. + * - cover: Resizes (shrinks or enlarges) to fill the entire area of width + * and height. If the image has an aspect ratio different from the ratio + * of width and height, it will be cropped to fit. + * - crop: The image will be shrunk and cropped to fit within the area + * specified by width and height. The image will not be enlarged. For images + * smaller than the given dimensions it's the same as scale-down. For + * images larger than the given dimensions, it's the same as cover. + * See also trim. + * - pad: Resizes to the maximum size that fits within the given width and + * height, and then fills the remaining area with a background color + * (white by default). Use of this mode is not recommended, as the same + * effect can be more efficiently achieved with the contain mode and the + * CSS object-fit: contain property. + * - squeeze: Stretches and deforms to the width and height given, even if it + * breaks aspect ratio + */ + fit?: "scale-down" | "contain" | "cover" | "crop" | "pad" | "squeeze"; + /** + * When cropping with fit: "cover", this defines the side or point that should + * be left uncropped. The value is either a string + * "left", "right", "top", "bottom", "auto", or "center" (the default), + * or an object {x, y} containing focal point coordinates in the original + * image expressed as fractions ranging from 0.0 (top or left) to 1.0 + * (bottom or right), 0.5 being the center. {fit: "cover", gravity: "top"} will + * crop bottom or left and right sides as necessary, but won’t crop anything + * from the top. {fit: "cover", gravity: {x:0.5, y:0.2}} will crop each side to + * preserve as much as possible around a point at 20% of the height of the + * source image. + */ + gravity?: 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | BasicImageTransformationsGravityCoordinates; + /** + * Background color to add underneath the image. Applies only to images with + * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…), + * hsl(…), etc.) + */ + background?: string; + /** + * Number of degrees (90, 180, 270) to rotate the image by. width and height + * options refer to axes after rotation. + */ + rotate?: 0 | 90 | 180 | 270 | 360; +} +interface BasicImageTransformationsGravityCoordinates { + x?: number; + y?: number; + mode?: 'remainder' | 'box-center'; +} +/** + * In addition to the properties you can set in the RequestInit dict + * that you pass as an argument to the Request constructor, you can + * set certain properties of a `cf` object to control how Cloudflare + * features are applied to that new Request. + * + * Note: Currently, these properties cannot be tested in the + * playground. + */ +interface RequestInitCfProperties extends Record { + cacheEverything?: boolean; + /** + * A request's cache key is what determines if two requests are + * "the same" for caching purposes. If a request has the same cache key + * as some previous request, then we can serve the same cached response for + * both. (e.g. 'some-key') + * + * Only available for Enterprise customers. + */ + cacheKey?: string; + /** + * This allows you to append additional Cache-Tag response headers + * to the origin response without modifications to the origin server. + * This will allow for greater control over the Purge by Cache Tag feature + * utilizing changes only in the Workers process. + * + * Only available for Enterprise customers. + */ + cacheTags?: string[]; + /** + * Force response to be cached for a given number of seconds. (e.g. 300) + */ + cacheTtl?: number; + /** + * Force response to be cached for a given number of seconds based on the Origin status code. + * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 }) + */ + cacheTtlByStatus?: Record; + scrapeShield?: boolean; + apps?: boolean; + image?: RequestInitCfPropertiesImage; + minify?: RequestInitCfPropertiesImageMinify; + mirage?: boolean; + polish?: "lossy" | "lossless" | "off"; + r2?: RequestInitCfPropertiesR2; + /** + * Redirects the request to an alternate origin server. You can use this, + * for example, to implement load balancing across several origins. + * (e.g.us-east.example.com) + * + * Note - For security reasons, the hostname set in resolveOverride must + * be proxied on the same Cloudflare zone of the incoming request. + * Otherwise, the setting is ignored. CNAME hosts are allowed, so to + * resolve to a host under a different domain or a DNS only domain first + * declare a CNAME record within your own zone’s DNS mapping to the + * external hostname, set proxy on Cloudflare, then set resolveOverride + * to point to that CNAME record. + */ + resolveOverride?: string; +} +interface RequestInitCfPropertiesImageDraw extends BasicImageTransformations { + /** + * Absolute URL of the image file to use for the drawing. It can be any of + * the supported file formats. For drawing of watermarks or non-rectangular + * overlays we recommend using PNG or WebP images. + */ + url: string; + /** + * Floating-point number between 0 (transparent) and 1 (opaque). + * For example, opacity: 0.5 makes overlay semitransparent. + */ + opacity?: number; + /** + * - If set to true, the overlay image will be tiled to cover the entire + * area. This is useful for stock-photo-like watermarks. + * - If set to "x", the overlay image will be tiled horizontally only + * (form a line). + * - If set to "y", the overlay image will be tiled vertically only + * (form a line). + */ + repeat?: true | "x" | "y"; + /** + * Position of the overlay image relative to a given edge. Each property is + * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10 + * positions left side of the overlay 10 pixels from the left edge of the + * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom + * of the background image. + * + * Setting both left & right, or both top & bottom is an error. + * + * If no position is specified, the image will be centered. + */ + top?: number; + left?: number; + bottom?: number; + right?: number; +} +interface RequestInitCfPropertiesImage extends BasicImageTransformations { + /** + * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it + * easier to specify higher-DPI sizes in . + */ + dpr?: number; + /** + * Allows you to trim your image. Takes dpr into account and is performed before + * resizing or rotation. + * + * It can be used as: + * - left, top, right, bottom - it will specify the number of pixels to cut + * off each side + * - width, height - the width/height you'd like to end up with - can be used + * in combination with the properties above + * - border - this will automatically trim the surroundings of an image based on + * it's color. It consists of three properties: + * - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit) + * - tolerance: difference from color to treat as color + * - keep: the number of pixels of border to keep + */ + trim?: "border" | { + top?: number; + bottom?: number; + left?: number; + right?: number; + width?: number; + height?: number; + border?: boolean | { + color?: string; + tolerance?: number; + keep?: number; + }; + }; + /** + * Quality setting from 1-100 (useful values are in 60-90 range). Lower values + * make images look worse, but load faster. The default is 85. It applies only + * to JPEG and WebP images. It doesn’t have any effect on PNG. + */ + quality?: number | "low" | "medium-low" | "medium-high" | "high"; + /** + * Output format to generate. It can be: + * - avif: generate images in AVIF format. + * - webp: generate images in Google WebP format. Set quality to 100 to get + * the WebP-lossless format. + * - json: instead of generating an image, outputs information about the + * image, in JSON format. The JSON object will contain image size + * (before and after resizing), source image’s MIME type, file size, etc. + * - jpeg: generate images in JPEG format. + * - png: generate images in PNG format. + */ + format?: "avif" | "webp" | "json" | "jpeg" | "png" | "baseline-jpeg" | "png-force" | "svg"; + /** + * Whether to preserve animation frames from input files. Default is true. + * Setting it to false reduces animations to still images. This setting is + * recommended when enlarging images or processing arbitrary user content, + * because large GIF animations can weigh tens or even hundreds of megabytes. + * It is also useful to set anim:false when using format:"json" to get the + * response quicker without the number of frames. + */ + anim?: boolean; + /** + * What EXIF data should be preserved in the output image. Note that EXIF + * rotation and embedded color profiles are always applied ("baked in" into + * the image), and aren't affected by this option. Note that if the Polish + * feature is enabled, all metadata may have been removed already and this + * option may have no effect. + * - keep: Preserve most of EXIF metadata, including GPS location if there's + * any. + * - copyright: Only keep the copyright tag, and discard everything else. + * This is the default behavior for JPEG files. + * - none: Discard all invisible EXIF metadata. Currently WebP and PNG + * output formats always discard metadata. + */ + metadata?: "keep" | "copyright" | "none"; + /** + * Strength of sharpening filter to apply to the image. Floating-point + * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a + * recommended value for downscaled images. + */ + sharpen?: number; + /** + * Radius of a blur filter (approximate gaussian). Maximum supported radius + * is 250. + */ + blur?: number; + /** + * Overlays are drawn in the order they appear in the array (last array + * entry is the topmost layer). + */ + draw?: RequestInitCfPropertiesImageDraw[]; + /** + * Fetching image from authenticated origin. Setting this property will + * pass authentication headers (Authorization, Cookie, etc.) through to + * the origin. + */ + "origin-auth"?: "share-publicly"; + /** + * Adds a border around the image. The border is added after resizing. Border + * width takes dpr into account, and can be specified either using a single + * width property, or individually for each side. + */ + border?: { + color: string; + width: number; + } | { + color: string; + top: number; + right: number; + bottom: number; + left: number; + }; + /** + * Increase brightness by a factor. A value of 1.0 equals no change, a value + * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright. + * 0 is ignored. + */ + brightness?: number; + /** + * Increase contrast by a factor. A value of 1.0 equals no change, a value of + * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is + * ignored. + */ + contrast?: number; + /** + * Increase exposure by a factor. A value of 1.0 equals no change, a value of + * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored. + */ + gamma?: number; + /** + * Increase contrast by a factor. A value of 1.0 equals no change, a value of + * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is + * ignored. + */ + saturation?: number; + /** + * Flips the images horizontally, vertically, or both. Flipping is applied before + * rotation, so if you apply flip=h,rotate=90 then the image will be flipped + * horizontally, then rotated by 90 degrees. + */ + flip?: 'h' | 'v' | 'hv'; + /** + * Slightly reduces latency on a cache miss by selecting a + * quickest-to-compress file format, at a cost of increased file size and + * lower image quality. It will usually override the format option and choose + * JPEG over WebP or AVIF. We do not recommend using this option, except in + * unusual circumstances like resizing uncacheable dynamically-generated + * images. + */ + compression?: "fast"; +} +interface RequestInitCfPropertiesImageMinify { + javascript?: boolean; + css?: boolean; + html?: boolean; +} +interface RequestInitCfPropertiesR2 { + /** + * Colo id of bucket that an object is stored in + */ + bucketColoId?: number; +} +/** + * Request metadata provided by Cloudflare's edge. + */ +type IncomingRequestCfProperties = IncomingRequestCfPropertiesBase & IncomingRequestCfPropertiesBotManagementEnterprise & IncomingRequestCfPropertiesCloudflareForSaaSEnterprise & IncomingRequestCfPropertiesGeographicInformation & IncomingRequestCfPropertiesCloudflareAccessOrApiShield; +interface IncomingRequestCfPropertiesBase extends Record { + /** + * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request. + * + * @example 395747 + */ + asn: number; + /** + * The organization which owns the ASN of the incoming request. + * + * @example "Google Cloud" + */ + asOrganization: string; + /** + * The original value of the `Accept-Encoding` header if Cloudflare modified it. + * + * @example "gzip, deflate, br" + */ + clientAcceptEncoding?: string; + /** + * The number of milliseconds it took for the request to reach your worker. + * + * @example 22 + */ + clientTcpRtt?: number; + /** + * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code) + * airport code of the data center that the request hit. + * + * @example "DFW" + */ + colo: string; + /** + * Represents the upstream's response to a + * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html) + * from cloudflare. + * + * For workers with no upstream, this will always be `1`. + * + * @example 3 + */ + edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus; + /** + * The HTTP Protocol the request used. + * + * @example "HTTP/2" + */ + httpProtocol: string; + /** + * The browser-requested prioritization information in the request object. + * + * If no information was set, defaults to the empty string `""` + * + * @example "weight=192;exclusive=0;group=3;group-weight=127" + * @default "" + */ + requestPriority: string; + /** + * The TLS version of the connection to Cloudflare. + * In requests served over plaintext (without TLS), this property is the empty string `""`. + * + * @example "TLSv1.3" + */ + tlsVersion: string; + /** + * The cipher for the connection to Cloudflare. + * In requests served over plaintext (without TLS), this property is the empty string `""`. + * + * @example "AEAD-AES128-GCM-SHA256" + */ + tlsCipher: string; + /** + * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake. + * + * If the incoming request was served over plaintext (without TLS) this field is undefined. + */ + tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata; +} +interface IncomingRequestCfPropertiesBotManagementBase { + /** + * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot, + * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human). + * + * @example 54 + */ + score: number; + /** + * A boolean value that is true if the request comes from a good bot, like Google or Bing. + * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots). + */ + verifiedBot: boolean; + /** + * A boolean value that is true if the request originates from a + * Cloudflare-verified proxy service. + */ + corporateProxy: boolean; + /** + * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources. + */ + staticResource: boolean; + /** + * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request). + */ + detectionIds: number[]; +} +interface IncomingRequestCfPropertiesBotManagement { + /** + * Results of Cloudflare's Bot Management analysis + */ + botManagement: IncomingRequestCfPropertiesBotManagementBase; + /** + * Duplicate of `botManagement.score`. + * + * @deprecated + */ + clientTrustScore: number; +} +interface IncomingRequestCfPropertiesBotManagementEnterprise extends IncomingRequestCfPropertiesBotManagement { + /** + * Results of Cloudflare's Bot Management analysis + */ + botManagement: IncomingRequestCfPropertiesBotManagementBase & { + /** + * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients + * across different destination IPs, Ports, and X509 certificates. + */ + ja3Hash: string; + }; +} +interface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise { + /** + * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/). + * + * This field is only present if you have Cloudflare for SaaS enabled on your account + * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)). + */ + hostMetadata: HostMetadata; +} +interface IncomingRequestCfPropertiesCloudflareAccessOrApiShield { + /** + * Information about the client certificate presented to Cloudflare. + * + * This is populated when the incoming request is served over TLS using + * either Cloudflare Access or API Shield (mTLS) + * and the presented SSL certificate has a valid + * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number) + * (i.e., not `null` or `""`). + * + * Otherwise, a set of placeholder values are used. + * + * The property `certPresented` will be set to `"1"` when + * the object is populated (i.e. the above conditions were met). + */ + tlsClientAuth: IncomingRequestCfPropertiesTLSClientAuth | IncomingRequestCfPropertiesTLSClientAuthPlaceholder; +} +/** + * Metadata about the request's TLS handshake + */ +interface IncomingRequestCfPropertiesExportedAuthenticatorMetadata { + /** + * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal + * + * @example "44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d" + */ + clientHandshake: string; + /** + * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal + * + * @example "44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d" + */ + serverHandshake: string; + /** + * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal + * + * @example "084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b" + */ + clientFinished: string; + /** + * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal + * + * @example "084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b" + */ + serverFinished: string; +} +/** + * Geographic data about the request's origin. + */ +interface IncomingRequestCfPropertiesGeographicInformation { + /** + * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from. + * + * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `"T1"`, indicating a request that originated over TOR. + * + * If Cloudflare is unable to determine where the request originated this property is omitted. + * + * The country code `"T1"` is used for requests originating on TOR. + * + * @example "GB" + */ + country?: Iso3166Alpha2Code | "T1"; + /** + * If present, this property indicates that the request originated in the EU + * + * @example "1" + */ + isEUCountry?: "1"; + /** + * A two-letter code indicating the continent the request originated from. + * + * @example "AN" + */ + continent?: ContinentCode; + /** + * The city the request originated from + * + * @example "Austin" + */ + city?: string; + /** + * Postal code of the incoming request + * + * @example "78701" + */ + postalCode?: string; + /** + * Latitude of the incoming request + * + * @example "30.27130" + */ + latitude?: string; + /** + * Longitude of the incoming request + * + * @example "-97.74260" + */ + longitude?: string; + /** + * Timezone of the incoming request + * + * @example "America/Chicago" + */ + timezone?: string; + /** + * If known, the ISO 3166-2 name for the first level region associated with + * the IP address of the incoming request + * + * @example "Texas" + */ + region?: string; + /** + * If known, the ISO 3166-2 code for the first-level region associated with + * the IP address of the incoming request + * + * @example "TX" + */ + regionCode?: string; + /** + * Metro code (DMA) of the incoming request + * + * @example "635" + */ + metroCode?: string; +} +/** Data about the incoming request's TLS certificate */ +interface IncomingRequestCfPropertiesTLSClientAuth { + /** Always `"1"`, indicating that the certificate was presented */ + certPresented: "1"; + /** + * Result of certificate verification. + * + * @example "FAILED:self signed certificate" + */ + certVerified: Exclude; + /** The presented certificate's revokation status. + * + * - A value of `"1"` indicates the certificate has been revoked + * - A value of `"0"` indicates the certificate has not been revoked + */ + certRevoked: "1" | "0"; + /** + * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) + * + * @example "CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" + */ + certIssuerDN: string; + /** + * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) + * + * @example "CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" + */ + certSubjectDN: string; + /** + * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted) + * + * @example "CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" + */ + certIssuerDNRFC2253: string; + /** + * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted) + * + * @example "CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" + */ + certSubjectDNRFC2253: string; + /** The certificate issuer's distinguished name (legacy policies) */ + certIssuerDNLegacy: string; + /** The certificate subject's distinguished name (legacy policies) */ + certSubjectDNLegacy: string; + /** + * The certificate's serial number + * + * @example "00936EACBE07F201DF" + */ + certSerial: string; + /** + * The certificate issuer's serial number + * + * @example "2489002934BDFEA34" + */ + certIssuerSerial: string; + /** + * The certificate's Subject Key Identifier + * + * @example "BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4" + */ + certSKI: string; + /** + * The certificate issuer's Subject Key Identifier + * + * @example "BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4" + */ + certIssuerSKI: string; + /** + * The certificate's SHA-1 fingerprint + * + * @example "6b9109f323999e52259cda7373ff0b4d26bd232e" + */ + certFingerprintSHA1: string; + /** + * The certificate's SHA-256 fingerprint + * + * @example "acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea" + */ + certFingerprintSHA256: string; + /** + * The effective starting date of the certificate + * + * @example "Dec 22 19:39:00 2018 GMT" + */ + certNotBefore: string; + /** + * The effective expiration date of the certificate + * + * @example "Dec 22 19:39:00 2018 GMT" + */ + certNotAfter: string; +} +/** Placeholder values for TLS Client Authorization */ +interface IncomingRequestCfPropertiesTLSClientAuthPlaceholder { + certPresented: "0"; + certVerified: "NONE"; + certRevoked: "0"; + certIssuerDN: ""; + certSubjectDN: ""; + certIssuerDNRFC2253: ""; + certSubjectDNRFC2253: ""; + certIssuerDNLegacy: ""; + certSubjectDNLegacy: ""; + certSerial: ""; + certIssuerSerial: ""; + certSKI: ""; + certIssuerSKI: ""; + certFingerprintSHA1: ""; + certFingerprintSHA256: ""; + certNotBefore: ""; + certNotAfter: ""; +} +/** Possible outcomes of TLS verification */ +declare type CertVerificationStatus = +/** Authentication succeeded */ +"SUCCESS" +/** No certificate was presented */ + | "NONE" +/** Failed because the certificate was self-signed */ + | "FAILED:self signed certificate" +/** Failed because the certificate failed a trust chain check */ + | "FAILED:unable to verify the first certificate" +/** Failed because the certificate not yet valid */ + | "FAILED:certificate is not yet valid" +/** Failed because the certificate is expired */ + | "FAILED:certificate has expired" +/** Failed for another unspecified reason */ + | "FAILED"; +/** + * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare. + */ +declare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus = 0 /** Unknown */ | 1 /** no keepalives (not found) */ | 2 /** no connection re-use, opening keepalive connection failed */ | 3 /** no connection re-use, keepalive accepted and saved */ | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */ | 5; /** connection re-use, accepted by the origin server */ +/** ISO 3166-1 Alpha-2 codes */ +declare type Iso3166Alpha2Code = "AD" | "AE" | "AF" | "AG" | "AI" | "AL" | "AM" | "AO" | "AQ" | "AR" | "AS" | "AT" | "AU" | "AW" | "AX" | "AZ" | "BA" | "BB" | "BD" | "BE" | "BF" | "BG" | "BH" | "BI" | "BJ" | "BL" | "BM" | "BN" | "BO" | "BQ" | "BR" | "BS" | "BT" | "BV" | "BW" | "BY" | "BZ" | "CA" | "CC" | "CD" | "CF" | "CG" | "CH" | "CI" | "CK" | "CL" | "CM" | "CN" | "CO" | "CR" | "CU" | "CV" | "CW" | "CX" | "CY" | "CZ" | "DE" | "DJ" | "DK" | "DM" | "DO" | "DZ" | "EC" | "EE" | "EG" | "EH" | "ER" | "ES" | "ET" | "FI" | "FJ" | "FK" | "FM" | "FO" | "FR" | "GA" | "GB" | "GD" | "GE" | "GF" | "GG" | "GH" | "GI" | "GL" | "GM" | "GN" | "GP" | "GQ" | "GR" | "GS" | "GT" | "GU" | "GW" | "GY" | "HK" | "HM" | "HN" | "HR" | "HT" | "HU" | "ID" | "IE" | "IL" | "IM" | "IN" | "IO" | "IQ" | "IR" | "IS" | "IT" | "JE" | "JM" | "JO" | "JP" | "KE" | "KG" | "KH" | "KI" | "KM" | "KN" | "KP" | "KR" | "KW" | "KY" | "KZ" | "LA" | "LB" | "LC" | "LI" | "LK" | "LR" | "LS" | "LT" | "LU" | "LV" | "LY" | "MA" | "MC" | "MD" | "ME" | "MF" | "MG" | "MH" | "MK" | "ML" | "MM" | "MN" | "MO" | "MP" | "MQ" | "MR" | "MS" | "MT" | "MU" | "MV" | "MW" | "MX" | "MY" | "MZ" | "NA" | "NC" | "NE" | "NF" | "NG" | "NI" | "NL" | "NO" | "NP" | "NR" | "NU" | "NZ" | "OM" | "PA" | "PE" | "PF" | "PG" | "PH" | "PK" | "PL" | "PM" | "PN" | "PR" | "PS" | "PT" | "PW" | "PY" | "QA" | "RE" | "RO" | "RS" | "RU" | "RW" | "SA" | "SB" | "SC" | "SD" | "SE" | "SG" | "SH" | "SI" | "SJ" | "SK" | "SL" | "SM" | "SN" | "SO" | "SR" | "SS" | "ST" | "SV" | "SX" | "SY" | "SZ" | "TC" | "TD" | "TF" | "TG" | "TH" | "TJ" | "TK" | "TL" | "TM" | "TN" | "TO" | "TR" | "TT" | "TV" | "TW" | "TZ" | "UA" | "UG" | "UM" | "US" | "UY" | "UZ" | "VA" | "VC" | "VE" | "VG" | "VI" | "VN" | "VU" | "WF" | "WS" | "YE" | "YT" | "ZA" | "ZM" | "ZW"; +/** The 2-letter continent codes Cloudflare uses */ +declare type ContinentCode = "AF" | "AN" | "AS" | "EU" | "NA" | "OC" | "SA"; +type CfProperties = IncomingRequestCfProperties | RequestInitCfProperties; +interface D1Meta { + duration: number; + size_after: number; + rows_read: number; + rows_written: number; + last_row_id: number; + changed_db: boolean; + changes: number; + /** + * The region of the database instance that executed the query. + */ + served_by_region?: string; + /** + * True if-and-only-if the database instance that executed the query was the primary. + */ + served_by_primary?: boolean; + timings?: { + /** + * The duration of the SQL query execution by the database instance. It doesn't include any network time. + */ + sql_duration_ms: number; + }; +} +interface D1Response { + success: true; + meta: D1Meta & Record; + error?: never; +} +type D1Result = D1Response & { + results: T[]; +}; +interface D1ExecResult { + count: number; + duration: number; +} +type D1SessionConstraint = +// Indicates that the first query should go to the primary, and the rest queries +// using the same D1DatabaseSession will go to any replica that is consistent with +// the bookmark maintained by the session (returned by the first query). +"first-primary" +// Indicates that the first query can go anywhere (primary or replica), and the rest queries +// using the same D1DatabaseSession will go to any replica that is consistent with +// the bookmark maintained by the session (returned by the first query). + | "first-unconstrained"; +type D1SessionBookmark = string; +declare abstract class D1Database { + prepare(query: string): D1PreparedStatement; + batch(statements: D1PreparedStatement[]): Promise[]>; + exec(query: string): Promise; + /** + * Creates a new D1 Session anchored at the given constraint or the bookmark. + * All queries executed using the created session will have sequential consistency, + * meaning that all writes done through the session will be visible in subsequent reads. + * + * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session. + */ + withSession(constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint): D1DatabaseSession; + /** + * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases. + */ + dump(): Promise; +} +declare abstract class D1DatabaseSession { + prepare(query: string): D1PreparedStatement; + batch(statements: D1PreparedStatement[]): Promise[]>; + /** + * @returns The latest session bookmark across all executed queries on the session. + * If no query has been executed yet, `null` is returned. + */ + getBookmark(): D1SessionBookmark | null; +} +declare abstract class D1PreparedStatement { + bind(...values: unknown[]): D1PreparedStatement; + first(colName: string): Promise; + first>(): Promise; + run>(): Promise>; + all>(): Promise>; + raw(options: { + columnNames: true; + }): Promise<[ + string[], + ...T[] + ]>; + raw(options?: { + columnNames?: false; + }): Promise; +} +// `Disposable` was added to TypeScript's standard lib types in version 5.2. +// To support older TypeScript versions, define an empty `Disposable` interface. +// Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2, +// but this will ensure type checking on older versions still passes. +// TypeScript's interface merging will ensure our empty interface is effectively +// ignored when `Disposable` is included in the standard lib. +interface Disposable { +} +/** + * An email message that can be sent from a Worker. + */ +interface EmailMessage { + /** + * Envelope From attribute of the email message. + */ + readonly from: string; + /** + * Envelope To attribute of the email message. + */ + readonly to: string; +} +/** + * An email message that is sent to a consumer Worker and can be rejected/forwarded. + */ +interface ForwardableEmailMessage extends EmailMessage { + /** + * Stream of the email message content. + */ + readonly raw: ReadableStream; + /** + * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers). + */ + readonly headers: Headers; + /** + * Size of the email message content. + */ + readonly rawSize: number; + /** + * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason. + * @param reason The reject reason. + * @returns void + */ + setReject(reason: string): void; + /** + * Forward this email message to a verified destination address of the account. + * @param rcptTo Verified destination address. + * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers). + * @returns A promise that resolves when the email message is forwarded. + */ + forward(rcptTo: string, headers?: Headers): Promise; + /** + * Reply to the sender of this email message with a new EmailMessage object. + * @param message The reply message. + * @returns A promise that resolves when the email message is replied. + */ + reply(message: EmailMessage): Promise; +} +/** + * A binding that allows a Worker to send email messages. + */ +interface SendEmail { + send(message: EmailMessage): Promise; +} +declare abstract class EmailEvent extends ExtendableEvent { + readonly message: ForwardableEmailMessage; +} +declare type EmailExportedHandler = (message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext) => void | Promise; +declare module "cloudflare:email" { + let _EmailMessage: { + prototype: EmailMessage; + new (from: string, to: string, raw: ReadableStream | string): EmailMessage; + }; + export { _EmailMessage as EmailMessage }; +} +/** + * Hello World binding to serve as an explanatory example. DO NOT USE + */ +interface HelloWorldBinding { + /** + * Retrieve the current stored value + */ + get(): Promise<{ + value: string; + ms?: number; + }>; + /** + * Set a new stored value + */ + set(value: string): Promise; +} +interface Hyperdrive { + /** + * Connect directly to Hyperdrive as if it's your database, returning a TCP socket. + * + * Calling this method returns an idential socket to if you call + * `connect("host:port")` using the `host` and `port` fields from this object. + * Pick whichever approach works better with your preferred DB client library. + * + * Note that this socket is not yet authenticated -- it's expected that your + * code (or preferably, the client library of your choice) will authenticate + * using the information in this class's readonly fields. + */ + connect(): Socket; + /** + * A valid DB connection string that can be passed straight into the typical + * client library/driver/ORM. This will typically be the easiest way to use + * Hyperdrive. + */ + readonly connectionString: string; + /* + * A randomly generated hostname that is only valid within the context of the + * currently running Worker which, when passed into `connect()` function from + * the "cloudflare:sockets" module, will connect to the Hyperdrive instance + * for your database. + */ + readonly host: string; + /* + * The port that must be paired the the host field when connecting. + */ + readonly port: number; + /* + * The username to use when authenticating to your database via Hyperdrive. + * Unlike the host and password, this will be the same every time + */ + readonly user: string; + /* + * The randomly generated password to use when authenticating to your + * database via Hyperdrive. Like the host field, this password is only valid + * within the context of the currently running Worker instance from which + * it's read. + */ + readonly password: string; + /* + * The name of the database to connect to. + */ + readonly database: string; +} +// Copyright (c) 2024 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +type ImageInfoResponse = { + format: 'image/svg+xml'; +} | { + format: string; + fileSize: number; + width: number; + height: number; +}; +type ImageTransform = { + width?: number; + height?: number; + background?: string; + blur?: number; + border?: { + color?: string; + width?: number; + } | { + top?: number; + bottom?: number; + left?: number; + right?: number; + }; + brightness?: number; + contrast?: number; + fit?: 'scale-down' | 'contain' | 'pad' | 'squeeze' | 'cover' | 'crop'; + flip?: 'h' | 'v' | 'hv'; + gamma?: number; + gravity?: 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | { + x?: number; + y?: number; + mode: 'remainder' | 'box-center'; + }; + rotate?: 0 | 90 | 180 | 270; + saturation?: number; + sharpen?: number; + trim?: "border" | { + top?: number; + bottom?: number; + left?: number; + right?: number; + width?: number; + height?: number; + border?: boolean | { + color?: string; + tolerance?: number; + keep?: number; + }; + }; +}; +type ImageDrawOptions = { + opacity?: number; + repeat?: boolean | string; + top?: number; + left?: number; + bottom?: number; + right?: number; +}; +type ImageOutputOptions = { + format: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'image/avif' | 'rgb' | 'rgba'; + quality?: number; + background?: string; +}; +interface ImagesBinding { + /** + * Get image metadata (type, width and height) + * @throws {@link ImagesError} with code 9412 if input is not an image + * @param stream The image bytes + */ + info(stream: ReadableStream): Promise; + /** + * Begin applying a series of transformations to an image + * @param stream The image bytes + * @returns A transform handle + */ + input(stream: ReadableStream): ImageTransformer; +} +interface ImageTransformer { + /** + * Apply transform next, returning a transform handle. + * You can then apply more transformations, draw, or retrieve the output. + * @param transform + */ + transform(transform: ImageTransform): ImageTransformer; + /** + * Draw an image on this transformer, returning a transform handle. + * You can then apply more transformations, draw, or retrieve the output. + * @param image The image (or transformer that will give the image) to draw + * @param options The options configuring how to draw the image + */ + draw(image: ReadableStream | ImageTransformer, options?: ImageDrawOptions): ImageTransformer; + /** + * Retrieve the image that results from applying the transforms to the + * provided input + * @param options Options that apply to the output e.g. output format + */ + output(options: ImageOutputOptions): Promise; +} +interface ImageTransformationResult { + /** + * The image as a response, ready to store in cache or return to users + */ + response(): Response; + /** + * The content type of the returned image + */ + contentType(): string; + /** + * The bytes of the response + */ + image(): ReadableStream; +} +interface ImagesError extends Error { + readonly code: number; + readonly message: string; + readonly stack?: string; +} +type Params

= Record; +type EventContext = { + request: Request>; + functionPath: string; + waitUntil: (promise: Promise) => void; + passThroughOnException: () => void; + next: (input?: Request | string, init?: RequestInit) => Promise; + env: Env & { + ASSETS: { + fetch: typeof fetch; + }; + }; + params: Params

; + data: Data; +}; +type PagesFunction = Record> = (context: EventContext) => Response | Promise; +type EventPluginContext = { + request: Request>; + functionPath: string; + waitUntil: (promise: Promise) => void; + passThroughOnException: () => void; + next: (input?: Request | string, init?: RequestInit) => Promise; + env: Env & { + ASSETS: { + fetch: typeof fetch; + }; + }; + params: Params

; + data: Data; + pluginArgs: PluginArgs; +}; +type PagesPluginFunction = Record, PluginArgs = unknown> = (context: EventPluginContext) => Response | Promise; +declare module "assets:*" { + export const onRequest: PagesFunction; +} +// Copyright (c) 2022-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +declare module "cloudflare:pipelines" { + export abstract class PipelineTransformationEntrypoint { + protected env: Env; + protected ctx: ExecutionContext; + constructor(ctx: ExecutionContext, env: Env); + /** + * run recieves an array of PipelineRecord which can be + * transformed and returned to the pipeline + * @param records Incoming records from the pipeline to be transformed + * @param metadata Information about the specific pipeline calling the transformation entrypoint + * @returns A promise containing the transformed PipelineRecord array + */ + public run(records: I[], metadata: PipelineBatchMetadata): Promise; + } + export type PipelineRecord = Record; + export type PipelineBatchMetadata = { + pipelineId: string; + pipelineName: string; + }; + export interface Pipeline { + /** + * The Pipeline interface represents the type of a binding to a Pipeline + * + * @param records The records to send to the pipeline + */ + send(records: T[]): Promise; + } +} +// PubSubMessage represents an incoming PubSub message. +// The message includes metadata about the broker, the client, and the payload +// itself. +// https://developers.cloudflare.com/pub-sub/ +interface PubSubMessage { + // Message ID + readonly mid: number; + // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT + readonly broker: string; + // The MQTT topic the message was sent on. + readonly topic: string; + // The client ID of the client that published this message. + readonly clientId: string; + // The unique identifier (JWT ID) used by the client to authenticate, if token + // auth was used. + readonly jti?: string; + // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker + // received the message from the client. + readonly receivedAt: number; + // An (optional) string with the MIME type of the payload, if set by the + // client. + readonly contentType: string; + // Set to 1 when the payload is a UTF-8 string + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063 + readonly payloadFormatIndicator: number; + // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays. + // You can use payloadFormatIndicator to inspect this before decoding. + payload: string | Uint8Array; +} +// JsonWebKey extended by kid parameter +interface JsonWebKeyWithKid extends JsonWebKey { + // Key Identifier of the JWK + readonly kid: string; +} +interface RateLimitOptions { + key: string; +} +interface RateLimitOutcome { + success: boolean; +} +interface RateLimit { + /** + * Rate limit a request based on the provided options. + * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/ + * @returns A promise that resolves with the outcome of the rate limit. + */ + limit(options: RateLimitOptions): Promise; +} +// Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need +// to referenced by `Fetcher`. This is included in the "importable" version of the types which +// strips all `module` blocks. +declare namespace Rpc { + // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s. + // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`. + // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to + // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape) + export const __RPC_STUB_BRAND: '__RPC_STUB_BRAND'; + export const __RPC_TARGET_BRAND: '__RPC_TARGET_BRAND'; + export const __WORKER_ENTRYPOINT_BRAND: '__WORKER_ENTRYPOINT_BRAND'; + export const __DURABLE_OBJECT_BRAND: '__DURABLE_OBJECT_BRAND'; + export const __WORKFLOW_ENTRYPOINT_BRAND: '__WORKFLOW_ENTRYPOINT_BRAND'; + export interface RpcTargetBranded { + [__RPC_TARGET_BRAND]: never; + } + export interface WorkerEntrypointBranded { + [__WORKER_ENTRYPOINT_BRAND]: never; + } + export interface DurableObjectBranded { + [__DURABLE_OBJECT_BRAND]: never; + } + export interface WorkflowEntrypointBranded { + [__WORKFLOW_ENTRYPOINT_BRAND]: never; + } + export type EntrypointBranded = WorkerEntrypointBranded | DurableObjectBranded | WorkflowEntrypointBranded; + // Types that can be used through `Stub`s + export type Stubable = RpcTargetBranded | ((...args: any[]) => any); + // Types that can be passed over RPC + // The reason for using a generic type here is to build a serializable subset of structured + // cloneable composite types. This allows types defined with the "interface" keyword to pass the + // serializable check as well. Otherwise, only types defined with the "type" keyword would pass. + type Serializable = + // Structured cloneables + BaseType + // Structured cloneable composites + | Map ? Serializable : never, T extends Map ? Serializable : never> | Set ? Serializable : never> | ReadonlyArray ? Serializable : never> | { + [K in keyof T]: K extends number | string ? Serializable : never; + } + // Special types + | Stub + // Serialized as stubs, see `Stubify` + | Stubable; + // Base type for all RPC stubs, including common memory management methods. + // `T` is used as a marker type for unwrapping `Stub`s later. + interface StubBase extends Disposable { + [__RPC_STUB_BRAND]: T; + dup(): this; + } + export type Stub = Provider & StubBase; + // This represents all the types that can be sent as-is over an RPC boundary + type BaseType = void | undefined | null | boolean | number | bigint | string | TypedArray | ArrayBuffer | DataView | Date | Error | RegExp | ReadableStream | WritableStream | Request | Response | Headers; + // Recursively rewrite all `Stubable` types with `Stub`s + // prettier-ignore + type Stubify = T extends Stubable ? Stub : T extends Map ? Map, Stubify> : T extends Set ? Set> : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends BaseType ? T : T extends { + [key: string | number]: any; + } ? { + [K in keyof T]: Stubify; + } : T; + // Recursively rewrite all `Stub`s with the corresponding `T`s. + // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies: + // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`. + // prettier-ignore + type Unstubify = T extends StubBase ? V : T extends Map ? Map, Unstubify> : T extends Set ? Set> : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends BaseType ? T : T extends { + [key: string | number]: unknown; + } ? { + [K in keyof T]: Unstubify; + } : T; + type UnstubifyAll = { + [I in keyof A]: Unstubify; + }; + // Utility type for adding `Provider`/`Disposable`s to `object` types only. + // Note `unknown & T` is equivalent to `T`. + type MaybeProvider = T extends object ? Provider : unknown; + type MaybeDisposable = T extends object ? Disposable : unknown; + // Type for method return or property on an RPC interface. + // - Stubable types are replaced by stubs. + // - Serializable types are passed by value, with stubable types replaced by stubs + // and a top-level `Disposer`. + // Everything else can't be passed over PRC. + // Technically, we use custom thenables here, but they quack like `Promise`s. + // Intersecting with `(Maybe)Provider` allows pipelining. + // prettier-ignore + type Result = R extends Stubable ? Promise> & Provider : R extends Serializable ? Promise & MaybeDisposable> & MaybeProvider : never; + // Type for method or property on an RPC interface. + // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s. + // Unwrapping `Stub`s allows calling with `Stubable` arguments. + // For properties, rewrite types to be `Result`s. + // In each case, unwrap `Promise`s. + type MethodOrProperty = V extends (...args: infer P) => infer R ? (...args: UnstubifyAll

) => Result> : Result>; + // Type for the callable part of an `Provider` if `T` is callable. + // This is intersected with methods/properties. + type MaybeCallableProvider = T extends (...args: any[]) => any ? MethodOrProperty : unknown; + // Base type for all other types providing RPC-like interfaces. + // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types. + // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC. + export type Provider = MaybeCallableProvider & { + [K in Exclude>]: MethodOrProperty; + }; +} +declare namespace Cloudflare { + interface Env { + } +} +declare module 'cloudflare:workers' { + export type RpcStub = Rpc.Stub; + export const RpcStub: { + new (value: T): Rpc.Stub; + }; + export abstract class RpcTarget implements Rpc.RpcTargetBranded { + [Rpc.__RPC_TARGET_BRAND]: never; + } + // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC + export abstract class WorkerEntrypoint implements Rpc.WorkerEntrypointBranded { + [Rpc.__WORKER_ENTRYPOINT_BRAND]: never; + protected ctx: ExecutionContext; + protected env: Env; + constructor(ctx: ExecutionContext, env: Env); + fetch?(request: Request): Response | Promise; + tail?(events: TraceItem[]): void | Promise; + trace?(traces: TraceItem[]): void | Promise; + scheduled?(controller: ScheduledController): void | Promise; + queue?(batch: MessageBatch): void | Promise; + test?(controller: TestController): void | Promise; + } + export abstract class DurableObject implements Rpc.DurableObjectBranded { + [Rpc.__DURABLE_OBJECT_BRAND]: never; + protected ctx: DurableObjectState; + protected env: Env; + constructor(ctx: DurableObjectState, env: Env); + fetch?(request: Request): Response | Promise; + alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; + webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; + webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise; + webSocketError?(ws: WebSocket, error: unknown): void | Promise; + } + export type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'; + export type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number; + export type WorkflowDelayDuration = WorkflowSleepDuration; + export type WorkflowTimeoutDuration = WorkflowSleepDuration; + export type WorkflowRetentionDuration = WorkflowSleepDuration; + export type WorkflowBackoff = 'constant' | 'linear' | 'exponential'; + export type WorkflowStepConfig = { + retries?: { + limit: number; + delay: WorkflowDelayDuration | number; + backoff?: WorkflowBackoff; + }; + timeout?: WorkflowTimeoutDuration | number; + }; + export type WorkflowEvent = { + payload: Readonly; + timestamp: Date; + instanceId: string; + }; + export type WorkflowStepEvent = { + payload: Readonly; + timestamp: Date; + type: string; + }; + export abstract class WorkflowStep { + do>(name: string, callback: () => Promise): Promise; + do>(name: string, config: WorkflowStepConfig, callback: () => Promise): Promise; + sleep: (name: string, duration: WorkflowSleepDuration) => Promise; + sleepUntil: (name: string, timestamp: Date | number) => Promise; + waitForEvent>(name: string, options: { + type: string; + timeout?: WorkflowTimeoutDuration | number; + }): Promise>; + } + export abstract class WorkflowEntrypoint | unknown = unknown> implements Rpc.WorkflowEntrypointBranded { + [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never; + protected ctx: ExecutionContext; + protected env: Env; + constructor(ctx: ExecutionContext, env: Env); + run(event: Readonly>, step: WorkflowStep): Promise; + } + export const env: Cloudflare.Env; +} +interface SecretsStoreSecret { + /** + * Get a secret from the Secrets Store, returning a string of the secret value + * if it exists, or throws an error if it does not exist + */ + get(): Promise; +} +declare module "cloudflare:sockets" { + function _connect(address: string | SocketAddress, options?: SocketOptions): Socket; + export { _connect as connect }; +} +declare namespace TailStream { + interface Header { + readonly name: string; + readonly value: string; + } + interface FetchEventInfo { + readonly type: "fetch"; + readonly method: string; + readonly url: string; + readonly cfJson: string; + readonly headers: Header[]; + } + interface JsRpcEventInfo { + readonly type: "jsrpc"; + readonly methodName: string; + } + interface ScheduledEventInfo { + readonly type: "scheduled"; + readonly scheduledTime: Date; + readonly cron: string; + } + interface AlarmEventInfo { + readonly type: "alarm"; + readonly scheduledTime: Date; + } + interface QueueEventInfo { + readonly type: "queue"; + readonly queueName: string; + readonly batchSize: number; + } + interface EmailEventInfo { + readonly type: "email"; + readonly mailFrom: string; + readonly rcptTo: string; + readonly rawSize: number; + } + interface TraceEventInfo { + readonly type: "trace"; + readonly traces: (string | null)[]; + } + interface HibernatableWebSocketEventInfoMessage { + readonly type: "message"; + } + interface HibernatableWebSocketEventInfoError { + readonly type: "error"; + } + interface HibernatableWebSocketEventInfoClose { + readonly type: "close"; + readonly code: number; + readonly wasClean: boolean; + } + interface HibernatableWebSocketEventInfo { + readonly type: "hibernatableWebSocket"; + readonly info: HibernatableWebSocketEventInfoClose | HibernatableWebSocketEventInfoError | HibernatableWebSocketEventInfoMessage; + } + interface Resume { + readonly type: "resume"; + readonly attachment?: any; + } + interface CustomEventInfo { + readonly type: "custom"; + } + interface FetchResponseInfo { + readonly type: "fetch"; + readonly statusCode: number; + } + type EventOutcome = "ok" | "canceled" | "exception" | "unknown" | "killSwitch" | "daemonDown" | "exceededCpu" | "exceededMemory" | "loadShed" | "responseStreamDisconnected" | "scriptNotFound"; + interface ScriptVersion { + readonly id: string; + readonly tag?: string; + readonly message?: string; + } + interface Trigger { + readonly traceId: string; + readonly invocationId: string; + readonly spanId: string; + } + interface Onset { + readonly type: "onset"; + readonly dispatchNamespace?: string; + readonly entrypoint?: string; + readonly executionModel: string; + readonly scriptName?: string; + readonly scriptTags?: string[]; + readonly scriptVersion?: ScriptVersion; + readonly trigger?: Trigger; + readonly info: FetchEventInfo | JsRpcEventInfo | ScheduledEventInfo | AlarmEventInfo | QueueEventInfo | EmailEventInfo | TraceEventInfo | HibernatableWebSocketEventInfo | Resume | CustomEventInfo; + } + interface Outcome { + readonly type: "outcome"; + readonly outcome: EventOutcome; + readonly cpuTime: number; + readonly wallTime: number; + } + interface Hibernate { + readonly type: "hibernate"; + } + interface SpanOpen { + readonly type: "spanOpen"; + readonly name: string; + readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes; + } + interface SpanClose { + readonly type: "spanClose"; + readonly outcome: EventOutcome; + } + interface DiagnosticChannelEvent { + readonly type: "diagnosticChannel"; + readonly channel: string; + readonly message: any; + } + interface Exception { + readonly type: "exception"; + readonly name: string; + readonly message: string; + readonly stack?: string; + } + interface Log { + readonly type: "log"; + readonly level: "debug" | "error" | "info" | "log" | "warn"; + readonly message: string; + } + interface Return { + readonly type: "return"; + readonly info?: FetchResponseInfo; + } + interface Link { + readonly type: "link"; + readonly label?: string; + readonly traceId: string; + readonly invocationId: string; + readonly spanId: string; + } + interface Attribute { + readonly name: string; + readonly value: string | string[] | boolean | boolean[] | number | number[] | bigint | bigint[]; + } + interface Attributes { + readonly type: "attributes"; + readonly info: Attribute[]; + } + type EventType = Onset | Outcome | Hibernate | SpanOpen | SpanClose | DiagnosticChannelEvent | Exception | Log | Return | Link | Attributes; + interface TailEvent { + readonly invocationId: string; + readonly spanId: string; + readonly timestamp: Date; + readonly sequence: number; + readonly event: Event; + } + type TailEventHandler = (event: TailEvent) => void | Promise; + type TailEventHandlerObject = { + outcome?: TailEventHandler; + hibernate?: TailEventHandler; + spanOpen?: TailEventHandler; + spanClose?: TailEventHandler; + diagnosticChannel?: TailEventHandler; + exception?: TailEventHandler; + log?: TailEventHandler; + return?: TailEventHandler; + link?: TailEventHandler; + attributes?: TailEventHandler; + }; + type TailEventHandlerType = TailEventHandler | TailEventHandlerObject; +} +// Copyright (c) 2022-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +/** + * Data types supported for holding vector metadata. + */ +type VectorizeVectorMetadataValue = string | number | boolean | string[]; +/** + * Additional information to associate with a vector. + */ +type VectorizeVectorMetadata = VectorizeVectorMetadataValue | Record; +type VectorFloatArray = Float32Array | Float64Array; +interface VectorizeError { + code?: number; + error: string; +} +/** + * Comparison logic/operation to use for metadata filtering. + * + * This list is expected to grow as support for more operations are released. + */ +type VectorizeVectorMetadataFilterOp = "$eq" | "$ne"; +/** + * Filter criteria for vector metadata used to limit the retrieved query result set. + */ +type VectorizeVectorMetadataFilter = { + [field: string]: Exclude | null | { + [Op in VectorizeVectorMetadataFilterOp]?: Exclude | null; + }; +}; +/** + * Supported distance metrics for an index. + * Distance metrics determine how other "similar" vectors are determined. + */ +type VectorizeDistanceMetric = "euclidean" | "cosine" | "dot-product"; +/** + * Metadata return levels for a Vectorize query. + * + * Default to "none". + * + * @property all Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data. + * @property indexed Return all metadata fields configured for indexing in the vector return set. This level of retrieval is "free" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings). + * @property none No indexed metadata will be returned. + */ +type VectorizeMetadataRetrievalLevel = "all" | "indexed" | "none"; +interface VectorizeQueryOptions { + topK?: number; + namespace?: string; + returnValues?: boolean; + returnMetadata?: boolean | VectorizeMetadataRetrievalLevel; + filter?: VectorizeVectorMetadataFilter; +} +/** + * Information about the configuration of an index. + */ +type VectorizeIndexConfig = { + dimensions: number; + metric: VectorizeDistanceMetric; +} | { + preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity +}; +/** + * Metadata about an existing index. + * + * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. + * See {@link VectorizeIndexInfo} for its post-beta equivalent. + */ +interface VectorizeIndexDetails { + /** The unique ID of the index */ + readonly id: string; + /** The name of the index. */ + name: string; + /** (optional) A human readable description for the index. */ + description?: string; + /** The index configuration, including the dimension size and distance metric. */ + config: VectorizeIndexConfig; + /** The number of records containing vectors within the index. */ + vectorsCount: number; +} +/** + * Metadata about an existing index. + */ +interface VectorizeIndexInfo { + /** The number of records containing vectors within the index. */ + vectorCount: number; + /** Number of dimensions the index has been configured for. */ + dimensions: number; + /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */ + processedUpToDatetime: number; + /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */ + processedUpToMutation: number; +} +/** + * Represents a single vector value set along with its associated metadata. + */ +interface VectorizeVector { + /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */ + id: string; + /** The vector values */ + values: VectorFloatArray | number[]; + /** The namespace this vector belongs to. */ + namespace?: string; + /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */ + metadata?: Record; +} +/** + * Represents a matched vector for a query along with its score and (if specified) the matching vector information. + */ +type VectorizeMatch = Pick, "values"> & Omit & { + /** The score or rank for similarity, when returned as a result */ + score: number; +}; +/** + * A set of matching {@link VectorizeMatch} for a particular query. + */ +interface VectorizeMatches { + matches: VectorizeMatch[]; + count: number; +} +/** + * Results of an operation that performed a mutation on a set of vectors. + * Here, `ids` is a list of vectors that were successfully processed. + * + * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. + * See {@link VectorizeAsyncMutation} for its post-beta equivalent. + */ +interface VectorizeVectorMutation { + /* List of ids of vectors that were successfully processed. */ + ids: string[]; + /* Total count of the number of processed vectors. */ + count: number; +} +/** + * Result type indicating a mutation on the Vectorize Index. + * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation. + */ +interface VectorizeAsyncMutation { + /** The unique identifier for the async mutation operation containing the changeset. */ + mutationId: string; +} +/** + * A Vectorize Vector Search Index for querying vectors/embeddings. + * + * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. + * See {@link Vectorize} for its new implementation. + */ +declare abstract class VectorizeIndex { + /** + * Get information about the currently bound index. + * @returns A promise that resolves with information about the current index. + */ + public describe(): Promise; + /** + * Use the provided vector to perform a similarity search across the index. + * @param vector Input vector that will be used to drive the similarity search. + * @param options Configuration options to massage the returned data. + * @returns A promise that resolves with matched and scored vectors. + */ + public query(vector: VectorFloatArray | number[], options?: VectorizeQueryOptions): Promise; + /** + * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown. + * @param vectors List of vectors that will be inserted. + * @returns A promise that resolves with the ids & count of records that were successfully processed. + */ + public insert(vectors: VectorizeVector[]): Promise; + /** + * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values. + * @param vectors List of vectors that will be upserted. + * @returns A promise that resolves with the ids & count of records that were successfully processed. + */ + public upsert(vectors: VectorizeVector[]): Promise; + /** + * Delete a list of vectors with a matching id. + * @param ids List of vector ids that should be deleted. + * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted). + */ + public deleteByIds(ids: string[]): Promise; + /** + * Get a list of vectors with a matching id. + * @param ids List of vector ids that should be returned. + * @returns A promise that resolves with the raw unscored vectors matching the id set. + */ + public getByIds(ids: string[]): Promise; +} +/** + * A Vectorize Vector Search Index for querying vectors/embeddings. + * + * Mutations in this version are async, returning a mutation id. + */ +declare abstract class Vectorize { + /** + * Get information about the currently bound index. + * @returns A promise that resolves with information about the current index. + */ + public describe(): Promise; + /** + * Use the provided vector to perform a similarity search across the index. + * @param vector Input vector that will be used to drive the similarity search. + * @param options Configuration options to massage the returned data. + * @returns A promise that resolves with matched and scored vectors. + */ + public query(vector: VectorFloatArray | number[], options?: VectorizeQueryOptions): Promise; + /** + * Use the provided vector-id to perform a similarity search across the index. + * @param vectorId Id for a vector in the index against which the index should be queried. + * @param options Configuration options to massage the returned data. + * @returns A promise that resolves with matched and scored vectors. + */ + public queryById(vectorId: string, options?: VectorizeQueryOptions): Promise; + /** + * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown. + * @param vectors List of vectors that will be inserted. + * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset. + */ + public insert(vectors: VectorizeVector[]): Promise; + /** + * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values. + * @param vectors List of vectors that will be upserted. + * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset. + */ + public upsert(vectors: VectorizeVector[]): Promise; + /** + * Delete a list of vectors with a matching id. + * @param ids List of vector ids that should be deleted. + * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset. + */ + public deleteByIds(ids: string[]): Promise; + /** + * Get a list of vectors with a matching id. + * @param ids List of vector ids that should be returned. + * @returns A promise that resolves with the raw unscored vectors matching the id set. + */ + public getByIds(ids: string[]): Promise; +} +/** + * The interface for "version_metadata" binding + * providing metadata about the Worker Version using this binding. + */ +type WorkerVersionMetadata = { + /** The ID of the Worker Version using this binding */ + id: string; + /** The tag of the Worker Version using this binding */ + tag: string; + /** The timestamp of when the Worker Version was uploaded */ + timestamp: string; +}; +interface DynamicDispatchLimits { + /** + * Limit CPU time in milliseconds. + */ + cpuMs?: number; + /** + * Limit number of subrequests. + */ + subRequests?: number; +} +interface DynamicDispatchOptions { + /** + * Limit resources of invoked Worker script. + */ + limits?: DynamicDispatchLimits; + /** + * Arguments for outbound Worker script, if configured. + */ + outbound?: { + [key: string]: any; + }; +} +interface DispatchNamespace { + /** + * @param name Name of the Worker script. + * @param args Arguments to Worker script. + * @param options Options for Dynamic Dispatch invocation. + * @returns A Fetcher object that allows you to send requests to the Worker script. + * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown. + */ + get(name: string, args?: { + [key: string]: any; + }, options?: DynamicDispatchOptions): Fetcher; +} +declare module 'cloudflare:workflows' { + /** + * NonRetryableError allows for a user to throw a fatal error + * that makes a Workflow instance fail immediately without triggering a retry + */ + export class NonRetryableError extends Error { + public constructor(message: string, name?: string); + } +} +declare abstract class Workflow { + /** + * Get a handle to an existing instance of the Workflow. + * @param id Id for the instance of this Workflow + * @returns A promise that resolves with a handle for the Instance + */ + public get(id: string): Promise; + /** + * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown. + * @param options Options when creating an instance including id and params + * @returns A promise that resolves with a handle for the Instance + */ + public create(options?: WorkflowInstanceCreateOptions): Promise; + /** + * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown. + * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached. + * @param batch List of Options when creating an instance including name and params + * @returns A promise that resolves with a list of handles for the created instances. + */ + public createBatch(batch: WorkflowInstanceCreateOptions[]): Promise; +} +type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'; +type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number; +type WorkflowRetentionDuration = WorkflowSleepDuration; +interface WorkflowInstanceCreateOptions { + /** + * An id for your Workflow instance. Must be unique within the Workflow. + */ + id?: string; + /** + * The event payload the Workflow instance is triggered with + */ + params?: PARAMS; + /** + * The retention policy for Workflow instance. + * Defaults to the maximum retention period available for the owner's account. + */ + retention?: { + successRetention?: WorkflowRetentionDuration; + errorRetention?: WorkflowRetentionDuration; + }; +} +type InstanceStatus = { + status: 'queued' // means that instance is waiting to be started (see concurrency limits) + | 'running' | 'paused' | 'errored' | 'terminated' // user terminated the instance while it was running + | 'complete' | 'waiting' // instance is hibernating and waiting for sleep or event to finish + | 'waitingForPause' // instance is finishing the current work to pause + | 'unknown'; + error?: string; + output?: object; +}; +interface WorkflowError { + code?: number; + message: string; +} +declare abstract class WorkflowInstance { + public id: string; + /** + * Pause the instance. + */ + public pause(): Promise; + /** + * Resume the instance. If it is already running, an error will be thrown. + */ + public resume(): Promise; + /** + * Terminate the instance. If it is errored, terminated or complete, an error will be thrown. + */ + public terminate(): Promise; + /** + * Restart the instance. + */ + public restart(): Promise; + /** + * Returns the current status of the instance. + */ + public status(): Promise; + /** + * Send an event to this instance. + */ + public sendEvent({ type, payload, }: { + type: string; + payload: unknown; + }): Promise; +} diff --git a/mcp-worker/wrangler.toml b/mcp-worker/wrangler.toml new file mode 100644 index 000000000..9c41ee956 --- /dev/null +++ b/mcp-worker/wrangler.toml @@ -0,0 +1,50 @@ +# Cloudflare Worker configuration for DevCycle MCP Server +name = "devcycle-mcp-server" +main = "src/index.ts" +compatibility_date = "2025-06-28" +compatibility_flags = ["nodejs_compat"] + +# Production route +[[routes]] +pattern = "mcp.devcycle.com/*" +zone_name = "devcycle.com" + +# Durable Objects configuration +[[migrations]] +new_sqlite_classes = ["DevCycleMCP"] +tag = "v1" + +[[durable_objects.bindings]] +class_name = "DevCycleMCP" +name = "MCP_OBJECT" + +# KV namespace for OAuth session storage +[[kv_namespaces]] +binding = "OAUTH_KV" +id = "511f2abf74904876ba3e4627154f4d86" + +# Environment variables +[vars] +NODE_ENV = "production" +API_BASE_URL = "https://api.devcycle.com" +AUTH0_DOMAIN = "auth.devcycle.com" +AUTH0_AUDIENCE = "https://api.devcycle.com/" +AUTH0_SCOPE = "openid profile email offline_access" + + +# Secrets (set via: wrangler secret put ) +# AUTH0_CLIENT_ID +# AUTH0_CLIENT_SECRET + +# AI binding for future features (not currently used) +# [ai] +# binding = "AI" + +# Observability +[observability] +enabled = true + +# Development configuration +[dev] +port = 8787 +local_protocol = "http" diff --git a/package.json b/package.json index f7a298020..203158622 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "parse-diff": "^0.9.0", "recast": "^0.21.5", "reflect-metadata": "^0.1.14", - "zod": "^3.24.2" + "zod": "3.24.1" }, "devDependencies": { "@babel/code-frame": "^7.27.1", @@ -130,6 +130,9 @@ "scripts": { "build": "shx rm -rf dist && tsc -b && oclif manifest", "build:tar": "oclif pack tarballs", + "build:worker": "cd mcp-worker && yarn build", + "deploy:worker": "cd mcp-worker && yarn deploy", + "dev:worker": "cd mcp-worker && yarn dev", "format": "prettier --write \"src/**/*.{ts,js,json}\" \"test/**/*.{ts,js,json}\" \"test-utils/**/*.{ts,js,json}\" \"*.{ts,js,json,md}\"", "format:check": "prettier --check \"src/**/*.{ts,js,json}\" \"test/**/*.{ts,js,json}\" \"test-utils/**/*.{ts,js,json}\" \"*.{ts,js,json,md}\"", "lint": "eslint . --config eslint.config.mjs", @@ -151,8 +154,12 @@ ], "types": "dist/index.d.ts", "packageManager": "yarn@4.9.2", + "workspaces": [ + "mcp-worker" + ], "resolutions": { "nanoid@3.3.1": "3.3.8", - "serialize-javascript@6.0.0": "^6.0.2" + "serialize-javascript@6.0.0": "^6.0.2", + "zod": "3.24.1" } } diff --git a/src/api/features.ts b/src/api/features.ts index ac5581ed0..4866a8127 100644 --- a/src/api/features.ts +++ b/src/api/features.ts @@ -12,6 +12,8 @@ import { } from './schemas' import 'reflect-metadata' import { buildHeaders } from './common' +import { GetFeatureAuditLogHistoryArgsSchema } from '../mcp/types' +import { z } from 'zod' const FEATURE_URL = '/v2/projects/:project/features' @@ -192,81 +194,27 @@ export const getFeatureAuditLogHistory = async ( token: string, projectKey: string, featureKey: string, - daysBack = 30, -): Promise<{ - timeline: Array<{ - id: string - timestamp: string - action: string - actor: { - name: string - email?: string - } - resource: { - type: string - name: string - key: string - } - changes: Array<{ - field: string - oldValue: unknown - newValue: unknown - }> - environment?: string - }> -}> => { + options: Omit< + z.infer, + 'feature_key' + > = {}, +): Promise => { try { - // Calculate the date threshold - const sinceDate = new Date() - sinceDate.setDate(sinceDate.getDate() - daysBack) - const startDate = sinceDate.toISOString() - - const params = { - startDate, - perPage: 100, - page: 1, - } - console.error(`feature history params: ${JSON.stringify(params)}`) - // Use the audit log API to get feature history const response = await axiosClient.get( `/v1/projects/${projectKey}/features/${featureKey}/audit`, { headers: buildHeaders(token), - params, + params: options, }, ) - console.error( - `feature history response: ${JSON.stringify(response.data)}`, - ) - - const auditLogs = response.data || [] - - // Transform audit log entries to timeline format - const timeline = auditLogs.map((entry: any) => ({ - id: entry._id || entry.id, - timestamp: entry.createdAt || entry.timestamp, - action: entry.action || 'unknown', - actor: { - name: entry.user?.name || entry.actor?.name || 'Unknown', - email: entry.user?.email || entry.actor?.email, - }, - resource: { - type: entry.resourceType || 'feature', - name: entry.resourceName || featureKey, - key: entry.resourceKey || featureKey, - }, - changes: entry.changes || [], - environment: entry.environment?.key || entry.environmentKey, - })) - - return { timeline } + return response.data || [] } catch (error) { // If audit log API fails, return empty result console.warn( 'Failed to fetch feature history from audit log:', error instanceof Error ? error.message : 'Unknown error', ) - return { timeline: [] } + return [] } } diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 0237950aa..000000000 --- a/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { run } from '@oclif/core' diff --git a/src/mcp/api/interface.test.ts b/src/mcp/api/interface.test.ts new file mode 100644 index 000000000..c033f471a --- /dev/null +++ b/src/mcp/api/interface.test.ts @@ -0,0 +1,251 @@ +import { expect } from '@oclif/test' +import { IDevCycleApiClient, IAuthContext } from './interface' +import { LocalDevCycleApiClient, LocalAuthContext } from './localApiClient' +import { WorkerDevCycleApiClient, WorkerAuthContext } from './workerApiClient' + +// Mock auth context for testing +class MockAuthContext implements IAuthContext { + constructor( + private authToken: string = 'test-token', + private orgId: string = 'test-org', + private projectKey: string = 'test-project', + private authenticated: boolean = true, + private hasProjectFlag: boolean = true, + ) {} + + getAuthToken(): string { + return this.authToken + } + + getOrgId(): string { + return this.orgId + } + + getProjectKey(): string { + return this.projectKey + } + + isAuthenticated(): boolean { + return this.authenticated + } + + hasProject(): boolean { + return this.hasProjectFlag + } +} + +describe('API Interface Abstraction', () => { + describe('IDevCycleApiClient Interface Compliance', () => { + it('should allow different implementations of the same interface', () => { + const mockAuth = new MockAuthContext() + + // Test that both implementations satisfy the interface + const workerClient: IDevCycleApiClient = + new WorkerDevCycleApiClient(mockAuth) + const mockLocal = { + executeWithLogging: async () => ({ success: true }), + executeWithDashboardLink: async () => ({ + result: { success: true }, + dashboardLink: 'https://app.devcycle.com', + }), + } as any + const localClient: IDevCycleApiClient = new LocalDevCycleApiClient( + mockLocal, + ) + + expect(workerClient).to.not.be.undefined + expect(localClient).to.not.be.undefined + expect(typeof workerClient.executeWithLogging).to.equal('function') + expect(typeof workerClient.executeWithDashboardLink).to.equal( + 'function', + ) + expect(typeof localClient.executeWithLogging).to.equal('function') + expect(typeof localClient.executeWithDashboardLink).to.equal( + 'function', + ) + }) + }) + + describe('WorkerAuthContext', () => { + it('should extract authentication from JWT claims', () => { + const jwtClaims = { + devcycle_token: 'worker-token', + org_id: 'worker-org', + access_token: 'fallback-token', + organization_id: 'fallback-org', + } + + const authContext = new WorkerAuthContext( + jwtClaims, + 'worker-project', + ) + + expect(authContext.getAuthToken()).to.equal('worker-token') + expect(authContext.getOrgId()).to.equal('worker-org') + expect(authContext.getProjectKey()).to.equal('worker-project') + expect(authContext.isAuthenticated()).to.be.true + expect(authContext.hasProject()).to.be.true + }) + + it('should use fallback fields when primary fields are missing', () => { + const jwtClaims = { + access_token: 'fallback-token', + organization_id: 'fallback-org', + } + + const authContext = new WorkerAuthContext(jwtClaims, 'test-project') + + expect(authContext.getAuthToken()).to.equal('fallback-token') + expect(authContext.getOrgId()).to.equal('fallback-org') + }) + + it('should handle missing authentication gracefully', () => { + const jwtClaims = {} + const authContext = new WorkerAuthContext(jwtClaims) + + expect(authContext.isAuthenticated()).to.be.false + expect(authContext.hasProject()).to.be.false + + expect(() => authContext.getAuthToken()).to.throw( + 'No DevCycle token found in JWT claims', + ) + expect(() => authContext.getOrgId()).to.throw( + 'No organization ID found in JWT claims', + ) + expect(() => authContext.getProjectKey()).to.throw( + 'No project key configured for Worker context', + ) + }) + + it('should allow setting project key dynamically', () => { + const jwtClaims = { + devcycle_token: 'token', + org_id: 'org', + } + const authContext = new WorkerAuthContext(jwtClaims) + + expect(authContext.hasProject()).to.be.false + + authContext.setProjectKey('dynamic-project') + + expect(authContext.hasProject()).to.be.true + expect(authContext.getProjectKey()).to.equal('dynamic-project') + }) + }) + + describe('WorkerDevCycleApiClient', () => { + it('should execute operations with Worker-specific authentication', async () => { + const jwtClaims = { + devcycle_token: 'worker-token', + org_id: 'worker-org', + } + const authContext = new WorkerAuthContext( + jwtClaims, + 'worker-project', + ) + const client = new WorkerDevCycleApiClient(authContext) + + const mockOperation = async ( + authToken: string, + projectKey: string | undefined, + ) => { + expect(authToken).to.equal('worker-token') + expect(projectKey).to.equal('worker-project') + return { data: 'success' } + } + + const result = await client.executeWithLogging( + 'testOperation', + { test: 'args' }, + mockOperation, + ) + + expect(result.data).to.equal('success') + }) + + it('should validate authentication before executing operations', async () => { + const jwtClaims = {} + const authContext = new WorkerAuthContext(jwtClaims) + const client = new WorkerDevCycleApiClient(authContext) + + const mockOperation = async () => ({ + data: 'should not reach here', + }) + + try { + await client.executeWithLogging( + 'testOperation', + {}, + mockOperation, + ) + expect.fail('Should have thrown authentication error') + } catch (error) { + expect((error as Error).message).to.contain( + 'Authentication required for Worker API operations', + ) + } + }) + + it('should validate project context when required', async () => { + const jwtClaims = { + devcycle_token: 'worker-token', + org_id: 'worker-org', + } + const authContext = new WorkerAuthContext(jwtClaims) // No project key + const client = new WorkerDevCycleApiClient(authContext) + + const mockOperation = async () => ({ + data: 'should not reach here', + }) + + try { + await client.executeWithLogging( + 'testOperation', + {}, + mockOperation, + true, + ) + expect.fail('Should have thrown project error') + } catch (error) { + expect((error as Error).message).to.contain( + 'Project context required for this operation', + ) + } + }) + + it('should generate dashboard links correctly', async () => { + const jwtClaims = { + devcycle_token: 'worker-token', + org_id: 'worker-org', + } + const authContext = new WorkerAuthContext( + jwtClaims, + 'worker-project', + ) + const client = new WorkerDevCycleApiClient(authContext) + + const mockOperation = async ( + authToken: string, + projectKey: string | undefined, + ) => ({ featureKey: 'test-feature' }) + const dashboardLink = ( + orgId: string, + projectKey: string | undefined, + result: any, + ) => + `https://app.devcycle.com/o/${orgId}/p/${projectKey}/features/${result.featureKey}` + + const result = await client.executeWithDashboardLink( + 'createFeature', + { name: 'Test Feature' }, + mockOperation, + dashboardLink, + ) + + expect(result.result.featureKey).to.equal('test-feature') + expect(result.dashboardLink).to.equal( + 'https://app.devcycle.com/o/worker-org/p/worker-project/features/test-feature', + ) + }) + }) +}) diff --git a/src/mcp/api/interface.ts b/src/mcp/api/interface.ts new file mode 100644 index 000000000..3e5543a8e --- /dev/null +++ b/src/mcp/api/interface.ts @@ -0,0 +1,160 @@ +/** + * API Client Interface Abstraction for DevCycle MCP + * + * This module provides interface abstractions that allow different API client implementations + * for local (stdio) and remote (Worker) MCP server contexts. The local implementation uses + * file system and environment variables for authentication, while the Worker implementation + * uses OAuth tokens from JWT claims. + */ + +/** + * Core API client interface that abstracts away authentication and execution details. + * This allows the same tool implementations to work with different authentication strategies: + * - Local: API keys and config files (DevCycleAuth) + * - Worker: OAuth tokens from JWT claims + */ +export interface IDevCycleApiClient { + /** + * Execute an API operation with consistent logging and error handling + * @param operationName - Name of the operation for logging purposes + * @param args - Arguments passed to the operation + * @param operation - The actual API operation function + * @param requiresProject - Whether this operation requires a project context (default: true) + */ + executeWithLogging( + operationName: string, + args: any, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, + requiresProject?: boolean, + ): Promise + + /** + * Execute an API operation and include dashboard links in the response + * @param operationName - Name of the operation for logging purposes + * @param args - Arguments passed to the operation + * @param operation - The actual API operation function + * @param dashboardLink - Function to generate dashboard link from result + */ + executeWithDashboardLink( + operationName: string, + args: any, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, + dashboardLink: ( + orgId: string, + projectKey: string | undefined, + result: T, + ) => string, + ): Promise<{ result: T; dashboardLink: string }> + + /** + * Set the selected project (Worker implementation only) + * This method is used by project selection tools to persist user's project choice + * @param projectKey - The project key to select + */ + setSelectedProject?(projectKey: string): Promise + + /** + * Check if a project key is currently available + * @returns True if a project key is available from any source + */ + hasProjectKey?(): Promise +} + +/** + * Authentication context for API operations + * This interface abstracts the different ways authentication can be provided + */ +export interface IAuthContext { + /** The authentication token to use for API calls */ + getAuthToken(): string + /** The organization ID for the current context */ + getOrgId(): string + /** The project key for the current context (optional for some operations) */ + getProjectKey(): string + /** Check if authentication is available */ + isAuthenticated(): boolean + /** Check if project context is available */ + hasProject(): boolean +} + +/** + * Configuration for creating API client instances + */ +export interface ApiClientConfig { + /** Base URL for the DevCycle API (defaults to production) */ + baseUrl?: string + /** Additional headers to include with all requests */ + headers?: Record + /** Request timeout in milliseconds */ + timeout?: number + /** Whether to enable debug logging */ + debug?: boolean +} + +/** + * Factory interface for creating API client instances + * This allows different implementations for local vs Worker contexts + */ +export interface IApiClientFactory { + /** + * Create an API client instance with the provided authentication context + */ + createClient( + authContext: IAuthContext, + config?: ApiClientConfig, + ): IDevCycleApiClient +} + +/** + * Error types that can be thrown by API operations + */ +export enum ApiErrorType { + AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR', + AUTHORIZATION_ERROR = 'AUTHORIZATION_ERROR', + NOT_FOUND_ERROR = 'NOT_FOUND_ERROR', + VALIDATION_ERROR = 'VALIDATION_ERROR', + NETWORK_ERROR = 'NETWORK_ERROR', + SCHEMA_VALIDATION_ERROR = 'SCHEMA_VALIDATION_ERROR', + PROJECT_ERROR = 'PROJECT_ERROR', + UNKNOWN_ERROR = 'UNKNOWN_ERROR', +} + +/** + * Structured error information for API operations + */ +export interface ApiError { + type: ApiErrorType + message: string + operation?: string + statusCode?: number + suggestions?: string[] + timestamp?: string +} + +/** + * Result wrapper for API operations that may fail + */ +export type ApiResult = + | { + success: true + data: T + } + | { + success: false + error: ApiError + } + +/** + * Utility type for dashboard link generator functions + */ +export type DashboardLinkGenerator = ( + orgId: string, + projectKey: string | undefined, + result: T, +) => string diff --git a/src/mcp/api/localApiClient.ts b/src/mcp/api/localApiClient.ts new file mode 100644 index 000000000..c6170981d --- /dev/null +++ b/src/mcp/api/localApiClient.ts @@ -0,0 +1,138 @@ +import { DevCycleApiClient } from '../utils/api' +import { DevCycleAuth } from '../utils/auth' +import { + IDevCycleApiClient, + IAuthContext, + IApiClientFactory, + ApiClientConfig, +} from './interface' + +/** + * Local authentication context that wraps DevCycleAuth for the interface abstraction. + * This maintains compatibility with existing local authentication while providing + * the standard interface for API clients. + */ +export class LocalAuthContext implements IAuthContext { + constructor(private auth: DevCycleAuth) {} + + getAuthToken(): string { + return this.auth.getAuthToken() + } + + getOrgId(): string { + return this.auth.getOrgId() + } + + getProjectKey(): string { + return this.auth.getProjectKey() + } + + isAuthenticated(): boolean { + return this.auth.hasToken() + } + + hasProject(): boolean { + try { + const projectKey = this.auth.getProjectKey() + return Boolean(projectKey) + } catch { + return false + } + } + + /** + * Get the underlying DevCycleAuth instance for direct access when needed + */ + getAuth(): DevCycleAuth { + return this.auth + } +} + +/** + * Local implementation of IDevCycleApiClient that wraps the existing DevCycleApiClient. + * This adapter allows the existing local authentication and API client logic + * to work with the new registry pattern while maintaining full backward compatibility. + */ +export class LocalDevCycleApiClient implements IDevCycleApiClient { + constructor(private apiClient: DevCycleApiClient) {} + + async executeWithLogging( + operationName: string, + args: any, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, + requiresProject = true, + ): Promise { + return await this.apiClient.executeWithLogging( + operationName, + args, + operation, + requiresProject, + ) + } + + async executeWithDashboardLink( + operationName: string, + args: any, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, + dashboardLink: ( + orgId: string, + projectKey: string | undefined, + result: T, + ) => string, + ): Promise<{ result: T; dashboardLink: string }> { + return await this.apiClient.executeWithDashboardLink( + operationName, + args, + operation, + dashboardLink, + ) + } + + /** + * Get the underlying DevCycleApiClient for cases where direct access is needed + */ + getApiClient(): DevCycleApiClient { + return this.apiClient + } +} + +/** + * Factory for creating local API client instances + * This provides a standardized way to create API clients for the local environment + */ +export class LocalApiClientFactory implements IApiClientFactory { + createClient( + authContext: IAuthContext, + config?: ApiClientConfig, + ): IDevCycleApiClient { + // For local implementation, we ignore the config for now and use existing DevCycleApiClient + // In the future, we could use the config to customize the local client behavior + + if (authContext instanceof LocalAuthContext) { + // Use the wrapped DevCycleAuth directly + const auth = authContext.getAuth() + const apiClient = new DevCycleApiClient(auth) + return new LocalDevCycleApiClient(apiClient) + } + + // If we have a different auth context, we'd need to create a DevCycleAuth wrapper + // For now, this is not implemented as it's not needed for current use cases + throw new Error('LocalApiClientFactory only supports LocalAuthContext') + } +} + +/** + * Convenience function to create a local API client with DevCycleAuth + */ +export function createLocalApiClient( + auth: DevCycleAuth, +): LocalDevCycleApiClient { + const apiClient = new DevCycleApiClient(auth) + return new LocalDevCycleApiClient(apiClient) +} diff --git a/src/mcp/api/workerApiClient.ts b/src/mcp/api/workerApiClient.ts new file mode 100644 index 000000000..105570665 --- /dev/null +++ b/src/mcp/api/workerApiClient.ts @@ -0,0 +1,174 @@ +import { IDevCycleApiClient, IAuthContext } from './interface' + +/** + * Worker implementation of IDevCycleApiClient for Cloudflare Worker environments. + * This implementation uses OAuth tokens from JWT claims instead of local file system + * and environment variables for authentication. + * + * Note: This is a skeleton implementation for Phase 1.2. Full implementation + * will be completed in Phase 2 (Cloudflare Worker Implementation). + */ +export class WorkerDevCycleApiClient implements IDevCycleApiClient { + constructor(private authContext: IAuthContext) {} + + async executeWithLogging( + operationName: string, + args: any, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, + requiresProject = true, + ): Promise { + // TODO: Implement Worker-specific logging that works in Cloudflare environment + // (no console.error, use structured logging for Workers) + + this.validateAuth(requiresProject) + + const authToken = this.authContext.getAuthToken() + const projectKey = + requiresProject && this.authContext.hasProject() + ? this.authContext.getProjectKey() + : undefined + + try { + return await operation(authToken, projectKey) + } catch (error) { + // TODO: Implement Worker-specific error handling + // (different from local implementation) + throw this.wrapError(error, operationName) + } + } + + async executeWithDashboardLink( + operationName: string, + args: any, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, + dashboardLink: ( + orgId: string, + projectKey: string | undefined, + result: T, + ) => string, + ): Promise<{ result: T; dashboardLink: string }> { + const result = await this.executeWithLogging( + operationName, + args, + operation, + ) + + const organizationId = this.authContext.getOrgId() + const projectKey = this.authContext.hasProject() + ? this.authContext.getProjectKey() + : undefined + const link = dashboardLink(organizationId, projectKey, result) + + return { + result, + dashboardLink: link, + } + } + + private validateAuth(requiresProject: boolean): void { + if (!this.authContext.isAuthenticated()) { + throw new Error('Authentication required for Worker API operations') + } + + if (requiresProject && !this.authContext.hasProject()) { + throw new Error('Project context required for this operation') + } + } + + private wrapError(error: unknown, operationName: string): Error { + // TODO: Implement Worker-specific error wrapping + // Convert errors to structured format suitable for Workers + if (error instanceof Error) { + return new Error( + `Worker API Error in ${operationName}: ${error.message}`, + ) + } + return new Error( + `Worker API Error in ${operationName}: ${String(error)}`, + ) + } +} + +/** + * Authentication context implementation for Cloudflare Worker environments. + * This extracts authentication information from OAuth JWT tokens instead of + * local file system and environment variables. + */ +export class WorkerAuthContext implements IAuthContext { + constructor( + private jwtClaims: Record, + private projectKey?: string, + ) {} + + getAuthToken(): string { + // TODO: Extract or exchange JWT for DevCycle API token + // This will be implemented in Phase 2 + const token = + this.jwtClaims.devcycle_token || this.jwtClaims.access_token + if (!token) { + throw new Error('No DevCycle token found in JWT claims') + } + return token + } + + getOrgId(): string { + // TODO: Extract organization ID from JWT claims + // This will be implemented in Phase 2 + const orgId = this.jwtClaims.org_id || this.jwtClaims.organization_id + if (!orgId) { + throw new Error('No organization ID found in JWT claims') + } + return orgId + } + + getProjectKey(): string { + if (!this.projectKey) { + throw new Error('No project key configured for Worker context') + } + return this.projectKey + } + + isAuthenticated(): boolean { + try { + this.getAuthToken() + this.getOrgId() + return true + } catch { + return false + } + } + + hasProject(): boolean { + return Boolean(this.projectKey) + } + + /** + * Set the project key for this auth context + * In the Worker environment, this might be set based on the MCP session + */ + setProjectKey(projectKey: string): void { + this.projectKey = projectKey + } +} + +/** + * Factory for creating Worker API client instances + */ +export class WorkerApiClientFactory { + /** + * Create a Worker API client from JWT claims + */ + static fromJwtClaims( + jwtClaims: Record, + projectKey?: string, + ): WorkerDevCycleApiClient { + const authContext = new WorkerAuthContext(jwtClaims, projectKey) + return new WorkerDevCycleApiClient(authContext) + } +} diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 03027efb7..0a486af78 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { Server } from '@modelcontextprotocol/sdk/server/index.js' +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { DevCycleMCPServer } from './server' import { readFileSync } from 'fs' @@ -53,23 +53,16 @@ async function main() { // This ensures that requests from the MCP server are properly identified setMCPHeaders(version) - const server = new Server( - { - name: 'devcycle', - version, - }, - { - capabilities: { - tools: {}, - }, - }, - ) + const mcpServer = new McpServer({ + name: 'DevCycle MCP Local Server', + version, + }) - const mcpServer = new DevCycleMCPServer(server) - await mcpServer.initialize() + const dvcMCPServer = new DevCycleMCPServer(mcpServer) + await dvcMCPServer.initialize() const transport = new StdioServerTransport() - await server.connect(transport) + await mcpServer.connect(transport) console.error('DevCycle MCP server running on stdio') } diff --git a/src/mcp/server.test.ts b/src/mcp/server.test.ts index 58e81bff2..63de73eda 100644 --- a/src/mcp/server.test.ts +++ b/src/mcp/server.test.ts @@ -1,17 +1,18 @@ import { expect } from '@oclif/test' import sinon from 'sinon' import * as assert from 'assert' -import { Server } from '@modelcontextprotocol/sdk/server/index.js' -import { - CallToolRequestSchema, - ListToolsRequestSchema, -} from '@modelcontextprotocol/sdk/types.js' +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import { DevCycleMCPServer } from './server' import { DevCycleAuth } from './utils/auth' import { DevCycleApiClient } from './utils/api' +import { + categorizeError, + getErrorSuggestions, + handleToolError, +} from './utils/errorHandling' describe('DevCycleMCPServer', () => { - let server: Server + let server: McpServer let mcpServer: DevCycleMCPServer let authStub: sinon.SinonStubbedInstance let apiClientStub: sinon.SinonStubbedInstance @@ -19,8 +20,7 @@ describe('DevCycleMCPServer', () => { beforeEach(() => { // Mock the MCP Server server = { - setRequestHandler: sinon.stub(), - onerror: undefined, + registerTool: sinon.stub(), } as any // Create stubs for dependencies @@ -52,9 +52,8 @@ describe('DevCycleMCPServer', () => { await mcpServer.initialize() sinon.assert.calledOnce(authStub.initialize) - sinon.assert.calledTwice( - server.setRequestHandler as sinon.SinonStub, - ) // ListTools and CallTool + // Verify that tools were registered (should be many calls to registerTool) + sinon.assert.called(server.registerTool as sinon.SinonStub) }) it('should fail gracefully with missing auth credentials', async () => { @@ -94,12 +93,14 @@ describe('DevCycleMCPServer', () => { } }) - it('should set up error handler for server', async () => { + it('should register multiple tools during initialization', async () => { authStub.initialize.resolves() await mcpServer.initialize() - expect(server.onerror).to.be.a('function') + // Should register many tools (37 total across all modules) + const registerToolStub = server.registerTool as sinon.SinonStub + expect(registerToolStub.callCount).to.be.greaterThan(30) }) }) @@ -109,67 +110,47 @@ describe('DevCycleMCPServer', () => { await mcpServer.initialize() }) - it('should register ListTools handler', () => { - sinon.assert.calledWith( - server.setRequestHandler as sinon.SinonStub, - ListToolsRequestSchema, - sinon.match.func, - ) - }) + it('should register all expected tools', () => { + const registerToolStub = server.registerTool as sinon.SinonStub - it('should register CallTool handler', () => { - sinon.assert.calledWith( - server.setRequestHandler as sinon.SinonStub, - CallToolRequestSchema, - sinon.match.func, - ) - }) - - it('should return all tool definitions when listing tools', async () => { - const setRequestHandlerStub = - server.setRequestHandler as sinon.SinonStub - const listToolsHandler = setRequestHandlerStub + // Verify specific tools were registered + const registeredToolNames = registerToolStub .getCalls() - .find((call) => call.args[0] === ListToolsRequestSchema) - ?.args[1] - - expect(listToolsHandler).to.be.a('function') - - const result = await listToolsHandler() - - expect(result).to.have.property('tools') - expect(result.tools).to.be.an('array') - expect(result.tools.length).to.be.greaterThan(0) + .map((call) => call.args[0]) // Check that we have tools from all categories - const toolNames = result.tools.map((tool: any) => tool.name) - expect(toolNames).to.include('list_features') - expect(toolNames).to.include('list_variables') - expect(toolNames).to.include('list_environments') - expect(toolNames).to.include('list_projects') + expect(registeredToolNames).to.include('list_features') + expect(registeredToolNames).to.include('list_variables') + expect(registeredToolNames).to.include('list_environments') + expect(registeredToolNames).to.include('list_projects') + expect(registeredToolNames).to.include('get_current_project') + expect(registeredToolNames).to.include( + 'get_self_targeting_identity', + ) }) - it('should handle unknown tool requests', async () => { - const setRequestHandlerStub = - server.setRequestHandler as sinon.SinonStub - const callToolHandler = setRequestHandlerStub - .getCalls() - .find((call) => call.args[0] === CallToolRequestSchema)?.args[1] + it('should register tools with proper configurations', () => { + const registerToolStub = server.registerTool as sinon.SinonStub - expect(callToolHandler).to.be.a('function') + // Check that each tool registration has the required parameters + registerToolStub.getCalls().forEach((call) => { + expect(call.args[0]).to.be.a('string') // tool name + expect(call.args[1]).to.be.an('object') // config + expect(call.args[1]).to.have.property('description') + expect(call.args[2]).to.be.a('function') // handler + }) + }) - const request = { - params: { - name: 'unknown_tool', - arguments: {}, - }, - } + it('should register tools with input schemas', () => { + const registerToolStub = server.registerTool as sinon.SinonStub - const result = await callToolHandler(request) + // Find a tool that should have an input schema + const listProjectsCall = registerToolStub + .getCalls() + .find((call) => call.args[0] === 'list_projects') - expect(result.content[0].text).to.contain( - 'Unknown tool: unknown_tool', - ) + expect(listProjectsCall).to.exist + expect(listProjectsCall!.args[1]).to.have.property('inputSchema') }) }) @@ -180,10 +161,6 @@ describe('DevCycleMCPServer', () => { }) it('should categorize authentication errors correctly', () => { - const categorizeError = (mcpServer as any).categorizeError.bind( - mcpServer, - ) - expect(categorizeError('401 Unauthorized')).to.equal( 'AUTHENTICATION_ERROR', ) @@ -193,10 +170,6 @@ describe('DevCycleMCPServer', () => { }) it('should categorize permission errors correctly', () => { - const categorizeError = (mcpServer as any).categorizeError.bind( - mcpServer, - ) - expect(categorizeError('403 Forbidden')).to.equal( 'PERMISSION_ERROR', ) @@ -206,10 +179,6 @@ describe('DevCycleMCPServer', () => { }) it('should categorize resource not found errors correctly', () => { - const categorizeError = (mcpServer as any).categorizeError.bind( - mcpServer, - ) - expect(categorizeError('404 Not Found')).to.equal( 'RESOURCE_NOT_FOUND', ) @@ -219,10 +188,6 @@ describe('DevCycleMCPServer', () => { }) it('should categorize validation errors correctly', () => { - const categorizeError = (mcpServer as any).categorizeError.bind( - mcpServer, - ) - expect(categorizeError('400 Bad Request')).to.equal( 'VALIDATION_ERROR', ) @@ -232,10 +197,6 @@ describe('DevCycleMCPServer', () => { }) it('should categorize schema validation errors correctly', () => { - const categorizeError = (mcpServer as any).categorizeError.bind( - mcpServer, - ) - expect(categorizeError('Zodios: invalid response')).to.equal( 'SCHEMA_VALIDATION_ERROR', ) @@ -248,10 +209,6 @@ describe('DevCycleMCPServer', () => { }) it('should categorize network errors correctly', () => { - const categorizeError = (mcpServer as any).categorizeError.bind( - mcpServer, - ) - expect(categorizeError('ENOTFOUND api.devcycle.com')).to.equal( 'NETWORK_ERROR', ) @@ -259,10 +216,6 @@ describe('DevCycleMCPServer', () => { }) it('should categorize project errors correctly', () => { - const categorizeError = (mcpServer as any).categorizeError.bind( - mcpServer, - ) - expect( categorizeError('The project test-key was not found'), ).to.equal('PROJECT_ERROR') @@ -272,14 +225,10 @@ describe('DevCycleMCPServer', () => { }) it('should provide helpful suggestions for authentication errors', () => { - const getErrorSuggestions = ( - mcpServer as any - ).getErrorSuggestions.bind(mcpServer) - const suggestions = getErrorSuggestions('AUTHENTICATION_ERROR') expect(suggestions).to.include( - 'Run "dvc login sso" to re-authenticate the devcycle cli', + 'Re-authenticate with DevCycle (run "dvc login sso" for CLI for local MCP or re-login through OAuth for remote MCP)', ) expect(suggestions).to.include( 'Verify your API credentials are correct', @@ -288,14 +237,10 @@ describe('DevCycleMCPServer', () => { }) it('should provide helpful suggestions for project errors', () => { - const getErrorSuggestions = ( - mcpServer as any - ).getErrorSuggestions.bind(mcpServer) - const suggestions = getErrorSuggestions('PROJECT_ERROR') expect(suggestions).to.include( - 'Run "dvc projects select" to choose a valid project', + 'Select a valid project (use "dvc projects select" in CLI or project selection tools in workers)', ) expect(suggestions).to.include('Verify the project key is correct') expect(suggestions).to.include( @@ -304,10 +249,6 @@ describe('DevCycleMCPServer', () => { }) it('should format tool errors with helpful information', async () => { - const handleToolError = (mcpServer as any).handleToolError.bind( - mcpServer, - ) - const result = handleToolError( new Error('401 Unauthorized'), 'test_tool', @@ -323,10 +264,6 @@ describe('DevCycleMCPServer', () => { }) it('should handle string errors', async () => { - const handleToolError = (mcpServer as any).handleToolError.bind( - mcpServer, - ) - const result = handleToolError('String error message', 'test_tool') const errorResponse = JSON.parse(result.content[0].text) @@ -336,10 +273,6 @@ describe('DevCycleMCPServer', () => { }) it('should handle object errors', async () => { - const handleToolError = (mcpServer as any).handleToolError.bind( - mcpServer, - ) - const errorObject = { code: 'ERR001', details: 'Something went wrong', @@ -359,29 +292,33 @@ describe('DevCycleMCPServer', () => { await mcpServer.initialize() }) - it('should execute valid tool requests successfully', async () => { - const setRequestHandlerStub = - server.setRequestHandler as sinon.SinonStub - const callToolHandler = setRequestHandlerStub - .getCalls() - .find((call) => call.args[0] === CallToolRequestSchema)?.args[1] + it('should register tool handlers that can handle errors', async () => { + const registerToolStub = server.registerTool as sinon.SinonStub - expect(callToolHandler).to.be.a('function') + // Get a tool handler that was registered + const toolCall = registerToolStub.getCalls()[0] + expect(toolCall).to.exist - // Test that the handler function exists and can be called - // For now, we'll just verify that unknown tools are handled correctly - const request = { - params: { - name: 'unknown_tool', - arguments: {}, - }, - } + const [toolName, config, handler] = toolCall.args + expect(toolName).to.be.a('string') + expect(config).to.be.an('object') + expect(handler).to.be.a('function') - const result = await callToolHandler(request) - expect(result.content[0].type).to.equal('text') - expect(result.content[0].text).to.contain( - 'Unknown tool: unknown_tool', - ) + // The handler should be wrapped with error handling + // We can't easily test the actual execution without setting up the full API client + // But we can verify the handler exists and is a function + }) + + it('should register handlers with error wrapping', () => { + const registerToolStub = server.registerTool as sinon.SinonStub + + // Verify that all registered handlers are wrapped functions + registerToolStub.getCalls().forEach((call) => { + const handler = call.args[2] + expect(handler).to.be.a('function') + // The handler should be async since it's wrapped with error handling + expect(handler.constructor.name).to.equal('AsyncFunction') + }) }) }) }) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 433bc0ea0..408b72e73 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -1,36 +1,17 @@ -import { Server } from '@modelcontextprotocol/sdk/server/index.js' -import { - CallToolRequestSchema, - ListToolsRequestSchema, - Tool, -} from '@modelcontextprotocol/sdk/types.js' +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' +import { Tool, ToolAnnotations } from '@modelcontextprotocol/sdk/types.js' import { DevCycleAuth } from './utils/auth' import { DevCycleApiClient } from './utils/api' +import { IDevCycleApiClient } from './api/interface' import Writer from '../ui/writer' -import { - featureToolDefinitions, - featureToolHandlers, -} from './tools/featureTools' -import { - environmentToolDefinitions, - environmentToolHandlers, -} from './tools/environmentTools' -import { - projectToolDefinitions, - projectToolHandlers, -} from './tools/projectTools' -import { - variableToolDefinitions, - variableToolHandlers, -} from './tools/variableTools' -import { - selfTargetingToolDefinitions, - selfTargetingToolHandlers, -} from './tools/selfTargetingTools' -import { - resultsToolDefinitions, - resultsToolHandlers, -} from './tools/resultsTools' +import { registerFeatureTools } from './tools/featureTools' +import { registerEnvironmentTools } from './tools/environmentTools' +import { registerVariableTools } from './tools/variableTools' +import { registerSelfTargetingTools } from './tools/selfTargetingTools' +import { registerResultsTools } from './tools/resultsTools' +import { registerProjectTools } from './tools/projectTools' +import { registerCustomPropertiesTools } from './tools/customPropertiesTools' +import { handleToolError } from './utils/errorHandling' // Environment variable to control output schema inclusion const ENABLE_OUTPUT_SCHEMAS = process.env.ENABLE_OUTPUT_SCHEMAS === 'true' @@ -38,14 +19,26 @@ if (ENABLE_OUTPUT_SCHEMAS) { console.error('DevCycle MCP Server - Output Schemas: ENABLED') } -const ENABLE_DVC_MCP_DEBUG = process.env.ENABLE_DVC_MCP_DEBUG === 'true' - // Tool handler function type export type ToolHandler = ( args: unknown, - apiClient: DevCycleApiClient, + apiClient: IDevCycleApiClient, ) => Promise +// Type for the server instance with our helper method +export type DevCycleMCPServerInstance = { + registerToolWithErrorHandling: ( + name: string, + config: { + description: string + annotations?: any + inputSchema?: any + outputSchema?: any + }, + handler: (args: any) => Promise, + ) => void +} + // Function to conditionally remove outputSchema from tool definitions const processToolDefinitions = (tools: Tool[]): Tool[] => { if (ENABLE_OUTPUT_SCHEMAS) { @@ -59,32 +52,12 @@ const processToolDefinitions = (tools: Tool[]): Tool[] => { }) } -// Combine all tool definitions -const allToolDefinitions: Tool[] = processToolDefinitions([ - ...featureToolDefinitions, - ...environmentToolDefinitions, - ...projectToolDefinitions, - ...variableToolDefinitions, - ...selfTargetingToolDefinitions, - ...resultsToolDefinitions, -]) - -// Combine all tool handlers -const allToolHandlers: Record = { - ...featureToolHandlers, - ...environmentToolHandlers, - ...projectToolHandlers, - ...variableToolHandlers, - ...selfTargetingToolHandlers, - ...resultsToolHandlers, -} - export class DevCycleMCPServer { private auth: DevCycleAuth private apiClient: DevCycleApiClient private writer: Writer - constructor(private server: Server) { + constructor(private server: McpServer) { this.writer = new Writer() this.writer.headless = true // Always headless for MCP this.auth = new DevCycleAuth() @@ -95,7 +68,6 @@ export class DevCycleMCPServer { try { await this.setupAuth() this.setupToolHandlers() - this.setupErrorHandling() } catch (error) { console.error('Failed to initialize MCP server:', error) throw error @@ -111,217 +83,43 @@ export class DevCycleMCPServer { } } - private handleToolError(error: unknown, toolName: string) { - console.error(`Error in tool handler ${toolName}:`, error) - - let errorMessage = 'Unknown error' - let errorType = 'UNKNOWN_ERROR' - let suggestions: string[] = [] - - if (error instanceof Error) { - errorMessage = error.message - errorType = this.categorizeError(error.message) - suggestions = this.getErrorSuggestions(errorType) - } else if (error && typeof error === 'string') { - errorMessage = error - } else if (error && typeof error === 'object') { - errorMessage = JSON.stringify(error) - } - - const errorResponse = { - error: true, - type: errorType, - message: errorMessage, - tool: toolName, - suggestions, - timestamp: new Date().toISOString(), - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(errorResponse, null, 2), - }, - ], - } - } - - private categorizeError(errorMessage: string): string { - const lowerMessage = errorMessage.toLowerCase() - - switch (true) { - case lowerMessage.includes('zodios: invalid response') || - lowerMessage.includes('invalid_type') || - lowerMessage.includes('expected object, received'): - return 'SCHEMA_VALIDATION_ERROR' - - case lowerMessage.includes('401') || - lowerMessage.includes('unauthorized'): - return 'AUTHENTICATION_ERROR' - - case lowerMessage.includes('403') || - lowerMessage.includes('forbidden'): - return 'PERMISSION_ERROR' - - case lowerMessage.includes('project') && - lowerMessage.includes('not found'): - return 'PROJECT_ERROR' - - case lowerMessage.includes('404') || - lowerMessage.includes('not found'): - return 'RESOURCE_NOT_FOUND' - - case lowerMessage.includes('400') || - lowerMessage.includes('bad request'): - return 'VALIDATION_ERROR' - - case lowerMessage.includes('429') || - lowerMessage.includes('rate limit'): - return 'RATE_LIMIT_ERROR' - - case lowerMessage.includes('enotfound') || - lowerMessage.includes('network'): - return 'NETWORK_ERROR' - - default: - return 'UNKNOWN_ERROR' - } - } - - private getErrorSuggestions(errorType: string): string[] { - switch (errorType) { - case 'SCHEMA_VALIDATION_ERROR': - return [ - 'The API response format has changed or is unexpected', - 'This may be a temporary API issue - try again in a moment', - 'Contact DevCycle support if the issue persists', - ] - - case 'AUTHENTICATION_ERROR': - return [ - 'Run "dvc login sso" to re-authenticate the devcycle cli', - 'Verify your API credentials are correct', - 'Check if your token has expired', - ] - - case 'PERMISSION_ERROR': - return [ - 'Verify your account has permissions for this operation', - 'Check if you have access to the selected project', - 'Contact your DevCycle admin for permissions', - ] - - case 'RESOURCE_NOT_FOUND': - return [ - 'Verify the resource key/ID is correct', - 'Check if the resource exists in the selected project', - "Ensure you're in the correct environment", - ] - - case 'VALIDATION_ERROR': - return [ - 'Check the provided parameters are valid', - 'Verify required fields are not missing', - 'Review parameter format and constraints', - ] - - case 'RATE_LIMIT_ERROR': - return [ - 'Wait a moment before trying again', - 'Consider reducing the frequency of requests', - ] - - case 'NETWORK_ERROR': - return [ - 'Check your internet connection', - 'Verify firewall settings allow DevCycle API access', - 'Try again in a few moments', - ] - - case 'PROJECT_ERROR': - return [ - 'Run "dvc projects select" to choose a valid project', - 'Verify the project key is correct', - 'Check if you have access to this project', - ] - - default: - return [] - } + // Helper method to register tools with automatic error handling + registerToolWithErrorHandling( + name: string, + config: { + description: string + inputSchema?: any + outputSchema?: any + annotations?: ToolAnnotations + }, + handler: (args: any) => Promise, + ) { + this.server.registerTool(name, config, async (args: any) => { + try { + const result = await handler(args) + + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(result, null, 2), + }, + ], + } as any + } catch (error) { + return handleToolError(error, name) + } + }) } private setupToolHandlers() { - this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools: allToolDefinitions, - })) - - this.server.setRequestHandler( - CallToolRequestSchema, - async (request) => { - const { name, arguments: args } = request.params - - try { - const handler = allToolHandlers[name] - if (!handler) { - throw new Error(`Unknown tool: ${name}`) - } - - const result = await handler(args, this.apiClient) - - // Return structured content only if output schemas are enabled - if (ENABLE_OUTPUT_SCHEMAS) { - // Check if tool has output schema - const toolDef = allToolDefinitions.find( - (tool) => tool.name === name, - ) - - if (toolDef?.outputSchema) { - // For tools with output schemas, return structured JSON content - const mcpResult = { - content: [ - { - type: 'json', - json: result, - }, - ], - } - if (ENABLE_DVC_MCP_DEBUG) { - console.error( - `MCP ${name} structured JSON result:`, - JSON.stringify(mcpResult, null, 2), - ) - } - return mcpResult - } - } - - // Default: return as text content (for disabled schemas or tools without schemas) - const mcpResult = { - content: [ - { - type: 'text', - text: JSON.stringify(result, null, 2), - }, - ], - } - if (ENABLE_DVC_MCP_DEBUG) { - console.error( - `MCP ${name} text result:`, - JSON.stringify(mcpResult, null, 2), - ) - } - return mcpResult - } catch (error) { - return this.handleToolError(error, name) - } - }, - ) - } - - private setupErrorHandling() { - this.server.onerror = (error: Error) => { - console.error('MCP Server Error:', error) - } + // Register project tools using the new direct registration pattern + registerProjectTools(this, this.apiClient) + registerCustomPropertiesTools(this, this.apiClient) + registerEnvironmentTools(this, this.apiClient) + registerFeatureTools(this, this.apiClient) + registerResultsTools(this, this.apiClient) + registerSelfTargetingTools(this, this.apiClient) + registerVariableTools(this, this.apiClient) } } diff --git a/src/mcp/tools/commonSchemas.ts b/src/mcp/tools/commonSchemas.ts deleted file mode 100644 index 9f86d7622..000000000 --- a/src/mcp/tools/commonSchemas.ts +++ /dev/null @@ -1,423 +0,0 @@ -/** - * Common schema definitions used across multiple MCP tool files - */ - -// ============================================================================= -// SHARED OUTPUT SCHEMA PROPERTIES -// ============================================================================= - -export const DASHBOARD_LINK_PROPERTY = { - type: 'string' as const, - format: 'uri' as const, - description: 'URL to view and manage resources in the DevCycle dashboard', -} - -export const MESSAGE_RESPONSE_SCHEMA = { - type: 'object' as const, - properties: { - message: { - type: 'string' as const, - }, - }, - required: ['message'], -} - -// ============================================================================= -// SHARED INPUT SCHEMA PROPERTIES -// ============================================================================= - -export const FEATURE_KEY_PROPERTY = { - type: 'string' as const, - description: - 'The key of the feature (unique, immutable, max 100 characters, pattern: ^[a-z0-9-_.]+$)', -} - -export const ENVIRONMENT_KEY_PROPERTY = { - type: 'string' as const, - description: - 'The key of the environment (unique, immutable, max 100 characters, pattern: ^[a-z0-9-_.]+$)', -} - -export const VARIATION_KEY_PROPERTY = { - type: 'string' as const, - description: - 'Unique variation key (unique, immutable, max 100 characters, pattern: ^[a-z0-9-_.]+$)', -} - -export const VARIABLE_KEY_PROPERTY = { - type: 'string' as const, - description: - 'The variable key (unique, immutable, max 100 characters, pattern: ^[a-z0-9-_.]+$)', -} - -export const PROJECT_KEY_PROPERTY = { - type: 'string' as const, - description: - 'The project key (unique, immutable, max 100 characters, pattern: ^[a-z0-9-_.]+$)', -} - -export const CUSTOM_PROPERTY_KEY_PROPERTY = { - type: 'string' as const, - description: - 'The custom property key (unique, immutable, max 100 characters, pattern: ^[a-z0-9-_.]+$)', -} - -// Filter type definitions based on DevCycle API swagger schemas - -export const ALL_FILTER_SCHEMA = { - type: 'object' as const, - description: 'Filter that matches all users', - properties: { - type: { - type: 'string' as const, - description: 'Filter type of this audience', - enum: ['all'] as const, - }, - }, - required: ['type'] as const, -} - -export const USER_FILTER_SCHEMA = { - type: 'object' as const, - description: 'Filter by basic user properties', - properties: { - type: { - type: 'string' as const, - description: 'Filter type of this audience', - enum: ['user'] as const, - }, - subType: { - type: 'string' as const, - description: 'Sub type of this filter', - enum: ['user_id', 'email', 'platform', 'deviceModel'] as const, - }, - comparator: { - type: 'string' as const, - description: 'Comparator to use', - enum: [ - '=', - '!=', - 'exist', - '!exist', - 'contain', - '!contain', - 'startWith', - '!startWith', - 'endWith', - '!endWith', - ] as const, - }, - values: { - type: 'array' as const, - description: - 'Array of values (required for all filters except exist/!exist)', - items: { - type: 'string' as const, - }, - }, - }, - required: ['type', 'subType', 'comparator'] as const, -} - -export const USER_COUNTRY_FILTER_SCHEMA = { - type: 'object' as const, - description: 'Filter by user country', - properties: { - type: { - type: 'string' as const, - description: 'Filter type of this audience', - enum: ['user'] as const, - }, - subType: { - type: 'string' as const, - description: 'Sub type of this filter', - enum: ['country'] as const, - }, - comparator: { - type: 'string' as const, - description: 'Comparator to use', - enum: [ - '=', - '!=', - 'exist', - '!exist', - 'contain', - '!contain', - 'startWith', - '!startWith', - 'endWith', - '!endWith', - ] as const, - }, - values: { - type: 'array' as const, - description: 'Array of country codes (e.g., CA, US)', - items: { - type: 'string' as const, - }, - }, - }, - required: ['type', 'subType', 'comparator'] as const, -} - -export const USER_APP_VERSION_FILTER_SCHEMA = { - type: 'object' as const, - description: 'Filter by application version', - properties: { - type: { - type: 'string' as const, - description: 'Filter type of this audience', - enum: ['user'] as const, - }, - subType: { - type: 'string' as const, - description: 'Sub type of this filter', - enum: ['appVersion'] as const, - }, - comparator: { - type: 'string' as const, - description: 'Comparator to use', - enum: ['=', '!=', '>', '>=', '<', '<=', 'exist', '!exist'] as const, - }, - values: { - type: 'array' as const, - description: 'Array of version strings (e.g., 1.0.2)', - items: { - type: 'string' as const, - }, - }, - }, - required: ['type', 'subType', 'comparator'] as const, -} - -export const USER_PLATFORM_VERSION_FILTER_SCHEMA = { - type: 'object' as const, - description: 'Filter by platform version', - properties: { - type: { - type: 'string' as const, - description: 'Filter type of this audience', - enum: ['user'] as const, - }, - subType: { - type: 'string' as const, - description: 'Sub type of this filter', - enum: ['platformVersion'] as const, - }, - comparator: { - type: 'string' as const, - description: 'Comparator to use', - enum: ['=', '!=', '>', '>=', '<', '<=', 'exist', '!exist'] as const, - }, - values: { - type: 'array' as const, - description: 'Array of platform version strings', - items: { - type: 'string' as const, - }, - }, - }, - required: ['type', 'subType', 'comparator'] as const, -} - -export const USER_CUSTOM_FILTER_SCHEMA = { - type: 'object' as const, - description: 'Filter by custom user data properties', - properties: { - type: { - type: 'string' as const, - description: 'Filter type of this audience', - enum: ['user'] as const, - }, - subType: { - type: 'string' as const, - description: 'Sub type of this filter', - enum: ['customData'] as const, - }, - comparator: { - type: 'string' as const, - description: 'Comparator to use', - enum: [ - '=', - '!=', - '>', - '>=', - '<', - '<=', - 'exist', - '!exist', - 'contain', - '!contain', - 'startWith', - '!startWith', - 'endWith', - '!endWith', - ] as const, - }, - dataKey: { - type: 'string' as const, - description: 'Data Key used for custom data', - minLength: 1, - }, - dataKeyType: { - type: 'string' as const, - description: 'Data Key Type used for custom data', - enum: ['String', 'Boolean', 'Number'] as const, - }, - values: { - type: 'array' as const, - description: 'Array of values (type depends on dataKeyType)', - items: { - anyOf: [ - { type: 'string' as const }, - { type: 'number' as const }, - { type: 'boolean' as const }, - ], - }, - }, - }, - required: [ - 'type', - 'subType', - 'comparator', - 'dataKey', - 'dataKeyType', - ] as const, -} - -export const AUDIENCE_MATCH_FILTER_SCHEMA = { - type: 'object' as const, - description: 'Filter by audience membership', - properties: { - type: { - type: 'string' as const, - enum: ['audienceMatch'] as const, - }, - comparator: { - type: 'string' as const, - enum: ['=', '!='] as const, - }, - _audiences: { - type: 'array' as const, - description: 'Array of audience IDs to match against', - items: { - type: 'string' as const, - }, - }, - }, - required: ['type'] as const, -} - -// Target Audience schema based on DevCycle API swagger definition -export const TARGET_AUDIENCE_PROPERTY = { - type: 'object' as const, - description: 'Audience definition for the target', - properties: { - name: { - type: 'string' as const, - description: - 'Audience display name, must be set for project-level audiences.', - example: 'Android Users', - maxLength: 100, - minLength: 1, - }, - filters: { - type: 'object' as const, - description: - 'Audience filters, describing logic for segmenting users', - properties: { - filters: { - type: 'array' as const, - description: 'Array of filter conditions', - items: { - anyOf: [ - ALL_FILTER_SCHEMA, - USER_FILTER_SCHEMA, - USER_COUNTRY_FILTER_SCHEMA, - USER_APP_VERSION_FILTER_SCHEMA, - USER_PLATFORM_VERSION_FILTER_SCHEMA, - USER_CUSTOM_FILTER_SCHEMA, - AUDIENCE_MATCH_FILTER_SCHEMA, - ], - }, - }, - operator: { - type: 'string' as const, - description: 'Operator type for combining filters', - enum: ['and', 'or'] as const, - }, - }, - required: ['filters', 'operator'] as const, - }, - }, - required: ['filters'] as const, -} - -// ============================================================================= -// RESULTS AND ANALYTICS PROPERTIES -// ============================================================================= - -export const EVALUATION_QUERY_PROPERTIES = { - startDate: { - type: 'number' as const, - description: 'Start date as Unix timestamp (milliseconds since epoch)', - }, - endDate: { - type: 'number' as const, - description: 'End date as Unix timestamp (milliseconds since epoch)', - }, - platform: { - type: 'string' as const, - description: 'Platform filter for evaluation results', - }, - variable: { - type: 'string' as const, - description: 'Variable key filter for evaluation results', - }, - environment: { - type: 'string' as const, - description: 'Environment key to filter results', - }, - period: { - type: 'string' as const, - enum: ['day', 'hour', 'month'] as const, - description: 'Time aggregation period for results', - }, - sdkType: { - type: 'string' as const, - enum: ['client', 'server', 'mobile', 'api'] as const, - description: 'Filter by SDK type', - }, -} - -export const EVALUATION_DATA_POINT_SCHEMA = { - type: 'object' as const, - properties: { - date: { - type: 'string' as const, - format: 'date-time' as const, - description: 'ISO timestamp for this data point', - }, - values: { - type: 'object' as const, - description: 'Evaluation values for this time period', - }, - }, - required: ['date', 'values'] as const, -} - -export const PROJECT_DATA_POINT_SCHEMA = { - type: 'object' as const, - properties: { - date: { - type: 'string' as const, - format: 'date-time' as const, - description: 'ISO timestamp for this data point', - }, - value: { - type: 'number' as const, - description: 'Total evaluations in this time period', - }, - }, - required: ['date', 'value'] as const, -} diff --git a/src/mcp/tools/customPropertiesTools.ts b/src/mcp/tools/customPropertiesTools.ts index 84dea71ff..97c935a6f 100644 --- a/src/mcp/tools/customPropertiesTools.ts +++ b/src/mcp/tools/customPropertiesTools.ts @@ -1,5 +1,5 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' +import { z } from 'zod' +import { handleZodiosValidationErrors } from '../utils/api' import { fetchCustomProperties, createCustomProperty, @@ -12,385 +12,193 @@ import { UpdateCustomPropertyArgsSchema, DeleteCustomPropertyArgsSchema, } from '../types' -import { ToolHandler } from '../server' -import { - DASHBOARD_LINK_PROPERTY, - MESSAGE_RESPONSE_SCHEMA, - CUSTOM_PROPERTY_KEY_PROPERTY, -} from './commonSchemas' +import { IDevCycleApiClient } from '../api/interface' +import { DevCycleMCPServerInstance } from '../server' // Helper function to generate custom properties dashboard links const generateCustomPropertiesDashboardLink = ( orgId: string, - projectKey: string, + projectKey: string | undefined, ): string => { + if (!projectKey) { + throw new Error( + 'Project key is required for custom properties dashboard link. Please select a project first.', + ) + } return `https://app.devcycle.com/o/${orgId}/p/${projectKey}/custom-properties` } -// ============================================================================= -// INPUT SCHEMAS -// ============================================================================= - -const CUSTOM_PROPERTY_PAGINATION_PROPERTIES = { - page: { - type: 'number' as const, - description: 'Page number', - minimum: 1, - default: 1, - }, - perPage: { - type: 'number' as const, - description: 'Items per page', - minimum: 1, - maximum: 1000, - default: 100, - }, - sortBy: { - type: 'string' as const, - description: 'Sort field', - enum: [ - 'createdAt', - 'updatedAt', - 'name', - 'key', - 'createdBy', - 'propertyKey', - ], - default: 'createdAt', - }, - sortOrder: { - type: 'string' as const, - description: 'Sort order', - enum: ['asc', 'desc'], - default: 'desc', - }, - search: { - type: 'string' as const, - description: 'Search query to filter custom properties', - minLength: 3, - }, - createdBy: { - type: 'string' as const, - description: 'Filter by creator', - }, +// Individual handler functions +export async function listCustomPropertiesHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'listCustomProperties', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for listing custom properties. Please select a project first.', + ) + } + return await handleZodiosValidationErrors( + () => fetchCustomProperties(authToken, projectKey), + 'fetchCustomProperties', + ) + }, + generateCustomPropertiesDashboardLink, + ) } -const CUSTOM_PROPERTY_TYPE_PROPERTY = { - type: 'string' as const, - enum: ['String', 'Boolean', 'Number'] as const, - description: 'Custom property type', +export async function createCustomPropertyHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'createCustomProperty', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for creating custom properties. Please select a project first.', + ) + } + return await handleZodiosValidationErrors( + () => createCustomProperty(authToken, projectKey, args), + 'createCustomProperty', + ) + }, + generateCustomPropertiesDashboardLink, + ) } -const CUSTOM_PROPERTY_SCHEMA_PROPERTY = { - type: 'object' as const, - description: 'Schema definition for the custom property', - properties: { - schemaType: { - type: 'string' as const, - enum: ['enum'], - description: 'Schema type', - }, - required: { - type: 'boolean' as const, - description: 'Whether the property is required', - }, - enumSchema: { - type: 'object' as const, - description: 'Enum schema configuration', - properties: { - allowedValues: { - type: 'array' as const, - description: 'Array of allowed values', - items: { - type: 'object' as const, - properties: { - label: { - type: 'string' as const, - description: 'Display label for the value', - }, - value: { - description: - 'The actual value (string or number)', - }, - }, - required: ['label', 'value'], - }, - }, - allowAdditionalValues: { - type: 'boolean' as const, - description: - 'Whether additional values are allowed beyond the enum', - }, - }, - }, - }, +export async function updateCustomPropertyHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'updateCustomProperty', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for updating custom properties. Please select a project first.', + ) + } + const { key, ...updateData } = args + + return await handleZodiosValidationErrors( + () => + updateCustomProperty( + authToken, + projectKey, + key, + updateData, + ), + 'updateCustomProperty', + ) + }, + generateCustomPropertiesDashboardLink, + ) } -const CUSTOM_PROPERTY_COMMON_PROPERTIES = { - key: CUSTOM_PROPERTY_KEY_PROPERTY, - name: { - type: 'string' as const, - description: 'Custom property name (max 100 characters)', - maxLength: 100, - }, - type: CUSTOM_PROPERTY_TYPE_PROPERTY, - propertyKey: { - type: 'string' as const, - description: - 'Property key used to identify the custom property in user data', - }, - schema: CUSTOM_PROPERTY_SCHEMA_PROPERTY, +export async function deleteCustomPropertyHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'deleteCustomProperty', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for deleting custom properties. Please select a project first.', + ) + } + await handleZodiosValidationErrors( + () => deleteCustomProperty(authToken, projectKey, args.key), + 'deleteCustomProperty', + ) + return { + message: `Custom property '${args.key}' deleted successfully`, + } + }, + generateCustomPropertiesDashboardLink, + ) } -// ============================================================================= -// OUTPUT SCHEMAS -// ============================================================================= - -const CUSTOM_PROPERTY_OBJECT_SCHEMA = { - type: 'object' as const, - description: 'A DevCycle custom property configuration', - properties: { - _id: { - type: 'string' as const, - description: 'Unique identifier for the custom property', - }, - key: CUSTOM_PROPERTY_KEY_PROPERTY, - name: { - type: 'string' as const, - description: 'Display name of the custom property', - }, - type: { - type: 'string' as const, - description: 'Custom property type (String, Boolean, Number)', - }, - propertyKey: { - type: 'string' as const, - description: 'Property key used in user data', - }, - schema: CUSTOM_PROPERTY_SCHEMA_PROPERTY, - _project: { - type: 'string' as const, - description: 'Associated project ID', - }, - _createdBy: { - type: 'string' as const, - description: 'User who created the custom property', - }, - createdAt: { - type: 'string' as const, - description: 'ISO timestamp when the custom property was created', - }, - updatedAt: { - type: 'string' as const, +/** + * Register custom properties tools with the MCP server using the new direct registration pattern + */ +export function registerCustomPropertiesTools( + serverInstance: DevCycleMCPServerInstance, + apiClient: IDevCycleApiClient, +): void { + serverInstance.registerToolWithErrorHandling( + 'list_custom_properties', + { description: - 'ISO timestamp when the custom property was last updated', - }, - }, - required: [ - '_id', - 'key', - 'name', - 'type', - 'propertyKey', - '_project', - '_createdBy', - 'createdAt', - 'updatedAt', - ], -} - -// ============================================================================= -// TOOL DEFINITIONS -// ============================================================================= - -export const customPropertiesToolDefinitions: Tool[] = [ - { - name: 'list_custom_properties', - description: - 'List custom properties in the current project. Include dashboard link in the response.', - annotations: { - title: 'List Custom Properties', - readOnlyHint: true, - }, - inputSchema: { - type: 'object', - properties: CUSTOM_PROPERTY_PAGINATION_PROPERTIES, - }, - outputSchema: { - type: 'object' as const, - properties: { - result: { - type: 'array' as const, - description: - 'Array of custom property objects in the project', - items: CUSTOM_PROPERTY_OBJECT_SCHEMA, - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, + 'List custom properties in the current project. Include dashboard link in the response.', + annotations: { + title: 'List Custom Properties', + readOnlyHint: true, }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'create_custom_property', - description: - 'Create a new custom property. Include dashboard link in the response.', - annotations: { - title: 'Create Custom Property', + inputSchema: ListCustomPropertiesArgsSchema.shape, }, - inputSchema: { - type: 'object', - properties: CUSTOM_PROPERTY_COMMON_PROPERTIES, - required: ['key', 'name', 'type', 'propertyKey'], + async (args: any) => { + const validatedArgs = ListCustomPropertiesArgsSchema.parse(args) + return await listCustomPropertiesHandler(validatedArgs, apiClient) }, - outputSchema: { - type: 'object' as const, - properties: { - result: CUSTOM_PROPERTY_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'create_custom_property', + { + description: + 'Create a new custom property. Include dashboard link in the response.', + annotations: { + title: 'Create Custom Property', }, - required: ['result', 'dashboardLink'], + inputSchema: UpsertCustomPropertyArgsSchema.shape, }, - }, - { - name: 'update_custom_property', - description: - 'Update an existing custom property. ⚠️ IMPORTANT: Custom property changes can affect feature flags in production environments. Always confirm with the user before updating custom properties for features that are active in production. Include dashboard link in the response.', - annotations: { - title: 'Update Custom Property', - destructiveHint: true, + async (args: any) => { + const validatedArgs = UpsertCustomPropertyArgsSchema.parse(args) + return await createCustomPropertyHandler(validatedArgs, apiClient) }, - inputSchema: { - type: 'object', - properties: CUSTOM_PROPERTY_COMMON_PROPERTIES, - required: ['key'], - }, - outputSchema: { - type: 'object' as const, - properties: { - result: CUSTOM_PROPERTY_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'update_custom_property', + { + description: + 'Update an existing custom property. ⚠️ IMPORTANT: Custom property changes can affect feature flags in production environments. Always confirm with the user before updating custom properties for features that are active in production. Include dashboard link in the response.', + annotations: { + title: 'Update Custom Property', + destructiveHint: true, }, - required: ['result', 'dashboardLink'], + inputSchema: UpdateCustomPropertyArgsSchema.shape, }, - }, - { - name: 'delete_custom_property', - description: - 'Delete a custom property. ⚠️ CRITICAL: Deleting a custom property will remove it from ALL environments including production. ALWAYS confirm with the user before deleting any custom property. Include dashboard link in the response.', - annotations: { - title: 'Delete Custom Property', - destructiveHint: true, + async (args: any) => { + const validatedArgs = UpdateCustomPropertyArgsSchema.parse(args) + return await updateCustomPropertyHandler(validatedArgs, apiClient) }, - inputSchema: { - type: 'object', - properties: { - key: CUSTOM_PROPERTY_KEY_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'delete_custom_property', + { + description: + 'Delete a custom property. ⚠️ CRITICAL: Deleting a custom property will remove it from ALL environments including production. ALWAYS confirm with the user before deleting any custom property. Include dashboard link in the response.', + annotations: { + title: 'Delete Custom Property', + destructiveHint: true, }, - required: ['key'], + inputSchema: DeleteCustomPropertyArgsSchema.shape, }, - outputSchema: { - type: 'object' as const, - properties: { - result: MESSAGE_RESPONSE_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], + async (args: any) => { + const validatedArgs = DeleteCustomPropertyArgsSchema.parse(args) + return await deleteCustomPropertyHandler(validatedArgs, apiClient) }, - }, -] - -export const customPropertiesToolHandlers: Record = { - list_custom_properties: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = ListCustomPropertiesArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'listCustomProperties', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => fetchCustomProperties(authToken, projectKey), - 'fetchCustomProperties', - ) - }, - generateCustomPropertiesDashboardLink, - ) - }, - create_custom_property: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = UpsertCustomPropertyArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'createCustomProperty', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => - createCustomProperty( - authToken, - projectKey, - validatedArgs, - ), - 'createCustomProperty', - ) - }, - generateCustomPropertiesDashboardLink, - ) - }, - update_custom_property: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = UpdateCustomPropertyArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'updateCustomProperty', - validatedArgs, - async (authToken, projectKey) => { - const { key, ...updateData } = validatedArgs - - return await handleZodiosValidationErrors( - () => - updateCustomProperty( - authToken, - projectKey, - key, - updateData, - ), - 'updateCustomProperty', - ) - }, - generateCustomPropertiesDashboardLink, - ) - }, - delete_custom_property: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = DeleteCustomPropertyArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'deleteCustomProperty', - validatedArgs, - async (authToken, projectKey) => { - await handleZodiosValidationErrors( - () => - deleteCustomProperty( - authToken, - projectKey, - validatedArgs.key, - ), - 'deleteCustomProperty', - ) - return { - message: `Custom property '${validatedArgs.key}' deleted successfully`, - } - }, - generateCustomPropertiesDashboardLink, - ) - }, + ) } diff --git a/src/mcp/tools/environmentTools.ts b/src/mcp/tools/environmentTools.ts index 57f360e7f..e2b8f39be 100644 --- a/src/mcp/tools/environmentTools.ts +++ b/src/mcp/tools/environmentTools.ts @@ -1,5 +1,5 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' +import { z } from 'zod' +import { handleZodiosValidationErrors } from '../utils/api' import { fetchEnvironments, fetchEnvironmentByKey, @@ -12,350 +12,204 @@ import { CreateEnvironmentArgsSchema, UpdateEnvironmentArgsSchema, } from '../types' -import { ToolHandler } from '../server' -import { - DASHBOARD_LINK_PROPERTY, - ENVIRONMENT_KEY_PROPERTY, -} from './commonSchemas' +import { IDevCycleApiClient } from '../api/interface' +import { DevCycleMCPServerInstance } from '../server' // Helper function to generate environment dashboard links const generateEnvironmentDashboardLink = ( orgId: string, - projectKey: string, + projectKey: string | undefined, ): string => { + if (!projectKey) { + throw new Error( + 'Project key is required for environment dashboard link. Please select a project using the selecting a project first.', + ) + } return `https://app.devcycle.com/o/${orgId}/settings/p/${projectKey}/environments` } -// ============================================================================= -// INPUT SCHEMAS -// ============================================================================= - -// Reusable schema components -const ENVIRONMENT_COLOR_PROPERTY = { - type: 'string' as const, - description: 'Color used to represent this environment in the UI', -} - -const ENVIRONMENT_TYPE_PROPERTY = { - type: 'string' as const, - enum: [ - 'development', - 'staging', - 'production', - 'disaster_recovery', - ] as const, -} - -const ENVIRONMENT_COMMON_PROPERTIES = { - key: ENVIRONMENT_KEY_PROPERTY, - name: { - type: 'string' as const, - }, - description: { - type: 'string' as const, - }, - color: ENVIRONMENT_COLOR_PROPERTY, - type: ENVIRONMENT_TYPE_PROPERTY, - settings: { - type: 'object' as const, - properties: { - appIconUri: { - type: 'string' as const, - description: 'URI for the app icon', - }, +// Individual handler functions +export async function listEnvironmentsHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'listEnvironments', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for listing environments. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => fetchEnvironments(authToken, projectKey), + 'listEnvironments', + ) }, - }, -} - -const ENVIRONMENT_PAGINATION_PROPERTIES = { - search: { - type: 'string' as const, - description: 'Search query to filter results (minimum 3 characters)', - minLength: 3, - }, - page: { - type: 'number' as const, - description: 'Page number (default: 1)', - minimum: 1, - }, - perPage: { - type: 'number' as const, - description: 'Number of items per page (default: 100, max: 1000)', - minimum: 1, - maximum: 1000, - }, - sortBy: { - type: 'string' as const, - enum: [ - 'createdAt', - 'updatedAt', - 'name', - 'key', - 'createdBy', - 'propertyKey', - ] as const, - description: 'Field to sort by', - }, - sortOrder: { - type: 'string' as const, - enum: ['asc', 'desc'] as const, - description: 'Sort order (default: desc)', - }, - createdBy: { - type: 'string' as const, - description: 'Filter by user who created the environment', - }, -} - -// ============================================================================= -// OUTPUT SCHEMAS -// ============================================================================= - -// Shared SDK key properties -const SDK_KEY_PROPERTIES = { - mobile: { - type: 'string' as const, - description: 'Mobile SDK key for client-side mobile applications', - }, - server: { - type: 'string' as const, - description: 'Server SDK key for server-side applications', - }, - client: { - type: 'string' as const, - description: 'Client SDK key for client-side web applications', - }, + generateEnvironmentDashboardLink, + ) } -// Output schema components -const SDK_KEYS_OBJECT_SCHEMA = { - type: 'object' as const, - properties: SDK_KEY_PROPERTIES, - required: ['mobile', 'server', 'client'], +export async function getSdkKeysHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'getSdkKeys', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for getting SDK keys. Please select a project using the selecting a project first.', + ) + } + const environment = await handleZodiosValidationErrors( + () => + fetchEnvironmentByKey( + authToken, + projectKey, + args.environmentKey, + ), + 'fetchEnvironmentByKey', + ) + + const sdkKeys = environment.sdkKeys + + if (args.keyType) { + return { + [args.keyType]: sdkKeys[args.keyType], + } + } else { + return { + mobile: sdkKeys.mobile, + server: sdkKeys.server, + client: sdkKeys.client, + } + } + }, + generateEnvironmentDashboardLink, + ) } -const ENVIRONMENT_OBJECT_SCHEMA = { - type: 'object' as const, - description: 'A DevCycle environment configuration', - properties: { - ...ENVIRONMENT_COMMON_PROPERTIES, - _id: { - type: 'string' as const, - description: 'Unique identifier for the environment', - }, - sdkKeys: SDK_KEYS_OBJECT_SCHEMA, - createdAt: { - type: 'string' as const, - description: 'ISO timestamp when the environment was created', - }, - updatedAt: { - type: 'string' as const, - description: 'ISO timestamp when the environment was last updated', +export async function createEnvironmentHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'createEnvironment', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for creating environments. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => createEnvironment(authToken, projectKey, args), + 'createEnvironment', + ) }, - }, - required: [ - '_id', - 'key', - 'name', - 'type', - 'sdkKeys', - 'createdAt', - 'updatedAt', - ], + generateEnvironmentDashboardLink, + ) } -// Complete output schema definitions -const ENVIRONMENT_OUTPUT_SCHEMA = { - type: 'object' as const, - properties: { - result: ENVIRONMENT_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], +export async function updateEnvironmentHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + const { key, ...updateParams } = args + + return await apiClient.executeWithDashboardLink( + 'updateEnvironment', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for updating environments. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => + updateEnvironment(authToken, projectKey, key, updateParams), + 'updateEnvironment', + ) + }, + generateEnvironmentDashboardLink, + ) } -// ============================================================================= -// TOOL DEFINITIONS -// ============================================================================= -export const environmentToolDefinitions: Tool[] = [ - { - name: 'list_environments', - description: - 'List environments in the current project. Include dashboard link in the response.', - annotations: { - title: 'List Environments', - readOnlyHint: true, - }, - inputSchema: { - type: 'object', - properties: ENVIRONMENT_PAGINATION_PROPERTIES, - }, - outputSchema: { - type: 'object' as const, +/** + * Register environment tools with the MCP server using the new direct registration pattern + */ +export function registerEnvironmentTools( + serverInstance: DevCycleMCPServerInstance, + apiClient: IDevCycleApiClient, +): void { + serverInstance.registerToolWithErrorHandling( + 'list_environments', + { description: - 'Response containing a list of environments and dashboard link', - properties: { - result: { - type: 'array' as const, - description: 'Array of environment objects in the project', - items: ENVIRONMENT_OBJECT_SCHEMA, - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, + 'List environments in the current project. Include dashboard link in the response.', + annotations: { + title: 'List Environments', + readOnlyHint: true, }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'get_sdk_keys', - description: - 'Get SDK keys for an environment. Include dashboard link in the response.', - annotations: { - title: 'Get SDK Keys', - readOnlyHint: true, + inputSchema: ListEnvironmentsArgsSchema.shape, }, - inputSchema: { - type: 'object', - properties: { - environmentKey: ENVIRONMENT_KEY_PROPERTY, - keyType: { - type: 'string' as const, - enum: ['mobile', 'server', 'client'] as const, - description: 'The type of SDK key to retrieve', - }, - }, - required: ['environmentKey'], + async (args: any) => { + const validatedArgs = ListEnvironmentsArgsSchema.parse(args) + return await listEnvironmentsHandler(validatedArgs, apiClient) }, - outputSchema: { - type: 'object' as const, - description: 'Response containing SDK keys and dashboard link', - properties: { - result: { - type: 'object' as const, - description: - 'SDK keys for the requested environment (filtered by keyType if specified)', - properties: SDK_KEY_PROPERTIES, - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'get_sdk_keys', + { + description: + 'Get SDK keys for an environment. Include dashboard link in the response.', + annotations: { + title: 'Get SDK Keys', + readOnlyHint: true, }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'create_environment', - description: - 'Create a new environment. Include dashboard link in the response.', - annotations: { - title: 'Create Environment', + inputSchema: GetSdkKeysArgsSchema.shape, }, - inputSchema: { - type: 'object', - properties: ENVIRONMENT_COMMON_PROPERTIES, - required: ['name', 'key'], + async (args: any) => { + const validatedArgs = GetSdkKeysArgsSchema.parse(args) + return await getSdkKeysHandler(validatedArgs, apiClient) }, - outputSchema: ENVIRONMENT_OUTPUT_SCHEMA, - }, - { - name: 'update_environment', - description: - 'Update an existing environment. Include dashboard link in the response.', - annotations: { - title: 'Update Environment', - }, - inputSchema: { - type: 'object', - properties: ENVIRONMENT_COMMON_PROPERTIES, - required: ['key'], - }, - outputSchema: ENVIRONMENT_OUTPUT_SCHEMA, - }, -] - -export const environmentToolHandlers: Record = { - list_environments: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = ListEnvironmentsArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'listEnvironments', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => fetchEnvironments(authToken, projectKey), - 'listEnvironments', - ) - }, - generateEnvironmentDashboardLink, - ) - }, - get_sdk_keys: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = GetSdkKeysArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'getSdkKeys', - validatedArgs, - async (authToken, projectKey) => { - const environment = await handleZodiosValidationErrors( - () => - fetchEnvironmentByKey( - authToken, - projectKey, - validatedArgs.environmentKey, - ), - 'fetchEnvironmentByKey', - ) - - const sdkKeys = environment.sdkKeys - - if (validatedArgs.keyType) { - return { - [validatedArgs.keyType]: sdkKeys[validatedArgs.keyType], - } - } else { - return { - mobile: sdkKeys.mobile, - server: sdkKeys.server, - client: sdkKeys.client, - } - } - }, - generateEnvironmentDashboardLink, - ) - }, - create_environment: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = CreateEnvironmentArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'createEnvironment', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => - createEnvironment(authToken, projectKey, validatedArgs), - 'createEnvironment', - ) - }, - generateEnvironmentDashboardLink, - ) - }, - update_environment: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = UpdateEnvironmentArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'updateEnvironment', - validatedArgs, - async (authToken, projectKey) => { - const { key, ...updateParams } = validatedArgs - return await handleZodiosValidationErrors( - () => - updateEnvironment( - authToken, - projectKey, - key, - updateParams, - ), - 'updateEnvironment', - ) - }, - generateEnvironmentDashboardLink, - ) - }, + ) + + // DISABLED: Environment creation/update tools + // serverInstance.registerToolWithErrorHandling( + // 'create_environment', + // { + // description: + // 'Create a new environment. Include dashboard link in the response.', + // annotations: { + // title: 'Create Environment', + // }, + // inputSchema: CreateEnvironmentArgsSchema.shape, + // }, + // async (args: any) => { + // const validatedArgs = CreateEnvironmentArgsSchema.parse(args) + // return await createEnvironmentHandler(validatedArgs, apiClient) + // }, + // ) + + // serverInstance.registerToolWithErrorHandling( + // 'update_environment', + // { + // description: + // 'Update an existing environment. Include dashboard link in the response.', + // annotations: { + // title: 'Update Environment', + // }, + // inputSchema: UpdateEnvironmentArgsSchema.shape, + // }, + // async (args: any) => { + // const validatedArgs = UpdateEnvironmentArgsSchema.parse(args) + // return await updateEnvironmentHandler(validatedArgs, apiClient) + // }, + // ) } diff --git a/src/mcp/tools/featureTools.ts b/src/mcp/tools/featureTools.ts index 2647ccc1d..a0aaf93e7 100644 --- a/src/mcp/tools/featureTools.ts +++ b/src/mcp/tools/featureTools.ts @@ -1,5 +1,4 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient } from '../utils/api' +import { z } from 'zod' import { fetchFeatures, createFeature, @@ -26,8 +25,7 @@ import { UpdateFeatureArgsSchema, UpdateFeatureStatusArgsSchema, DeleteFeatureArgsSchema, - EnableTargetingArgsSchema, - DisableTargetingArgsSchema, + SetFeatureTargetingArgsSchema, ListVariationsArgsSchema, CreateVariationArgsSchema, UpdateVariationArgsSchema, @@ -35,1172 +33,636 @@ import { UpdateFeatureTargetingArgsSchema, GetFeatureAuditLogHistoryArgsSchema, } from '../types' -import { ToolHandler } from '../server' -import { - DASHBOARD_LINK_PROPERTY, - MESSAGE_RESPONSE_SCHEMA, - FEATURE_KEY_PROPERTY, - ENVIRONMENT_KEY_PROPERTY, - VARIATION_KEY_PROPERTY, - TARGET_AUDIENCE_PROPERTY, -} from './commonSchemas' +import { IDevCycleApiClient } from '../api/interface' +import { DevCycleMCPServerInstance } from '../server' import { handleZodiosValidationErrors } from '../utils/api' -// Helper function to generate feature dashboard links +// Helper functions to generate feature dashboard links const generateFeaturesDashboardLink = ( orgId: string, - projectKey: string, + projectKey: string | undefined, ): string => { + if (!projectKey) { + throw new Error( + 'Project key is required for features dashboard link. Please select a project using the selecting a project first.', + ) + } return `https://app.devcycle.com/o/${orgId}/p/${projectKey}/features` } const generateFeatureDashboardLink = ( orgId: string, - projectKey: string, + projectKey: string | undefined, featureKey: string, page: 'overview' | 'manage-feature' | 'audit-log' = 'overview', ): string => { + if (!projectKey) { + throw new Error( + 'Project key is required for feature dashboard link. Please select a project using the selecting a project first.', + ) + } return `https://app.devcycle.com/o/${orgId}/p/${projectKey}/features/${featureKey}/${page}` } -// ============================================================================= -// INPUT SCHEMAS -// ============================================================================= - -const ENVIRONMENT_KEY_OPTIONAL_PROPERTY = { - type: 'string' as const, - description: 'Optional environment key to filter by', -} - -const FEATURE_NAME_PROPERTY = { - type: 'string' as const, - description: 'Human-readable feature name (max 100 characters)', -} - -const FEATURE_DESCRIPTION_PROPERTY = { - type: 'string' as const, - description: 'Feature description (max 1000 characters)', +// Individual handler functions +export async function listFeaturesHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'listFeatures', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => fetchFeatures(authToken, projectKey, args), + 'listFeatures', + ) + }, + generateFeaturesDashboardLink, + ) } -const FEATURE_TYPE_PROPERTY = { - type: 'string' as const, - enum: ['release', 'experiment', 'permission', 'ops'] as const, - description: 'Feature type', +export async function createFeatureHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + if (!args.key || !args.name) { + throw new Error('Feature key and name are required') + } + + return await apiClient.executeWithDashboardLink( + 'createFeature', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => createFeature(authToken, projectKey, args), + 'createFeature', + ) + }, + (orgId: string, projectKey: string | undefined, result: any) => + generateFeatureDashboardLink( + orgId, + projectKey, + result.key, + 'overview', + ), + ) } -const FEATURE_STATUS_PROPERTY = { - type: 'string' as const, - enum: ['active', 'complete', 'archived'] as const, - description: 'Feature status', +export async function updateFeatureHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + const { key, ...updateData } = args + + return await apiClient.executeWithDashboardLink( + 'updateFeature', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => updateFeature(authToken, projectKey, key, updateData), + 'updateFeature', + ) + }, + (orgId: string, projectKey: string | undefined, result: any) => + generateFeatureDashboardLink( + orgId, + projectKey, + result.key, + 'manage-feature', + ), + ) } -const CONTROL_VARIATION_PROPERTY = { - type: 'string' as const, - description: - 'The key of the variation that is used as the control variation for Metrics', +export async function updateFeatureStatusHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + const { key, ...statusData } = args + + return await apiClient.executeWithDashboardLink( + 'updateFeatureStatus', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => + updateFeatureStatus(authToken, projectKey, key, statusData), + 'updateFeatureStatus', + ) + }, + (orgId: string, projectKey: string | undefined, result: any) => + generateFeatureDashboardLink( + orgId, + projectKey, + result.key, + 'overview', + ), + ) } -const FEATURE_PAGINATION_PROPERTIES = { - page: { - type: 'number' as const, - description: 'Page number', - minimum: 1, - default: 1, - }, - perPage: { - type: 'number' as const, - description: 'Items per page', - minimum: 1, - maximum: 1000, - default: 100, - }, - sortBy: { - type: 'string' as const, - description: 'Sort field', - enum: [ - 'createdAt', - 'updatedAt', - 'name', - 'key', - 'createdBy', - 'propertyKey', - ], - default: 'createdAt', - }, - sortOrder: { - type: 'string' as const, - description: 'Sort order', - enum: ['asc', 'desc'], - default: 'desc', - }, - search: { - type: 'string' as const, - description: 'Search query to filter results', - minLength: 3, - }, - createdBy: { - type: 'string' as const, - description: 'Filter by creator', - }, - type: { - type: 'string' as const, - description: 'Filter by feature type', - enum: ['release', 'experiment', 'permission', 'ops'], - }, - status: { - type: 'string' as const, - description: 'Filter by feature status', - enum: ['active', 'complete', 'archived'], - }, - staleness: { - type: 'string' as const, - description: 'Filter by feature staleness', - enum: ['all', 'unused', 'released', 'unmodified', 'notStale'], - }, +export async function deleteFeatureHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'deleteFeature', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + await handleZodiosValidationErrors( + () => deleteFeature(authToken, projectKey, args.key), + 'deleteFeature', + ) + return { + message: `Feature '${args.key}' deleted successfully`, + } + }, + generateFeaturesDashboardLink, + ) } -const FEATURE_SETTINGS_PROPERTY = { - type: 'object' as const, - description: 'Feature-level settings (all properties required if provided)', - properties: { - publicName: { - type: 'string' as const, - description: 'Public name for the feature (max 100 characters)', - }, - publicDescription: { - type: 'string' as const, - description: - 'Public description for the feature (max 1000 characters)', - }, - optInEnabled: { - type: 'boolean' as const, - description: 'Whether opt-in is enabled for the feature', - }, - }, - required: ['publicName', 'publicDescription', 'optInEnabled'] as const, +export async function fetchFeatureVariationsHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'fetchFeatureVariations', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => fetchVariations(authToken, projectKey, args.feature_key), + 'fetchVariations', + ) + }, + (orgId: string, projectKey: string | undefined) => + generateFeatureDashboardLink( + orgId, + projectKey, + args.feature_key, + 'overview', + ), + ) } -const SDK_VISIBILITY_PROPERTY = { - type: 'object' as const, - properties: { - mobile: { - type: 'boolean' as const, - }, - client: { - type: 'boolean' as const, - }, - server: { - type: 'boolean' as const, - }, - }, - description: - 'SDK Type Visibility Settings for mobile, client, and server SDKs', - required: ['mobile', 'client', 'server'] as const, +export async function createFeatureVariationHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + const { feature_key, ...variationData } = args + + return await apiClient.executeWithDashboardLink( + 'createFeatureVariation', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => + createVariation( + authToken, + projectKey, + feature_key, + variationData, + ), + 'createVariation', + ) + }, + (orgId: string, projectKey: string | undefined, result: any) => + generateFeatureDashboardLink( + orgId, + projectKey, + result.key, + 'manage-feature', + ), + ) } -const FEATURE_VARIABLES_PROPERTY = { - type: 'array' as const, - description: - 'Array of variables to create or reassociate with this feature', - items: { - type: 'object' as const, - description: 'Variable creation or reassociation data', - }, +export async function updateFeatureVariationHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + const { feature_key, variation_key, ...variationData } = args + + return await apiClient.executeWithDashboardLink( + 'updateFeatureVariation', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => + updateVariation( + authToken, + projectKey, + feature_key, + variation_key, + variationData, + ), + 'updateVariation', + ) + }, + (orgId: string, projectKey: string | undefined, result: any) => + generateFeatureDashboardLink( + orgId, + projectKey, + result.key, + 'manage-feature', + ), + ) } -const VARIATION_NAME_PROPERTY = { - type: 'string' as const, - description: 'Human-readable variation name (max 100 characters)', +export async function setFeatureTargetingHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + const operation = args.enabled ? 'enableTargeting' : 'disableTargeting' + const apiFunction = args.enabled ? enableTargeting : disableTargeting + + return await apiClient.executeWithDashboardLink( + operation, + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please selecting a project first.', + ) + } + await handleZodiosValidationErrors( + () => + apiFunction( + authToken, + projectKey, + args.feature_key, + args.environment_key, + ), + operation, + ) + const action = args.enabled ? 'enabled' : 'disabled' + return { + message: `Targeting ${action} for feature '${args.feature_key}' in environment '${args.environment_key}'`, + } + }, + (orgId: string, projectKey: string | undefined) => + generateFeatureDashboardLink( + orgId, + projectKey, + args.feature_key, + 'manage-feature', + ), + ) } -const VARIATION_VARIABLES_PROPERTY = { - type: 'object' as const, - description: - 'Key-value map of variable keys to their values for this variation (supports string, number, boolean, and object values)', - additionalProperties: true, +export async function listFeatureTargetingHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'listFeatureTargeting', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => + fetchTargetingForFeature( + authToken, + projectKey, + args.feature_key, + args.environment_key, + ), + 'fetchTargetingForFeature', + ) + }, + (orgId: string, projectKey: string | undefined) => + generateFeatureDashboardLink( + orgId, + projectKey, + args.feature_key, + 'manage-feature', + ), + ) } -// ============================================================================= -// OUTPUT SCHEMAS -// ============================================================================= - -const FEATURE_OBJECT_SCHEMA = { - type: 'object' as const, - description: 'A DevCycle feature configuration', - properties: { - _id: { - type: 'string' as const, - description: 'MongoDB ID for the feature', - }, - key: FEATURE_KEY_PROPERTY, - name: FEATURE_NAME_PROPERTY, - description: FEATURE_DESCRIPTION_PROPERTY, - type: FEATURE_TYPE_PROPERTY, - status: FEATURE_STATUS_PROPERTY, - variations: { - type: 'array' as const, - description: 'Array of variations for this feature', - }, - createdAt: { - type: 'string' as const, - description: 'ISO timestamp when the feature was created', - }, - updatedAt: { - type: 'string' as const, - description: 'ISO timestamp when the feature was last updated', - }, - }, - required: [ - '_id', - 'key', - 'name', - 'type', - 'status', - 'createdAt', - 'updatedAt', - ], +export async function updateFeatureTargetingHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + const { feature_key, environment_key, ...configData } = args + + return await apiClient.executeWithDashboardLink( + 'updateFeatureTargeting', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => + updateFeatureConfigForEnvironment( + authToken, + projectKey, + feature_key, + environment_key, + configData, + ), + 'updateFeatureConfigForEnvironment', + ) + }, + (orgId: string, projectKey: string | undefined) => + generateFeatureDashboardLink( + orgId, + projectKey, + args.feature_key, + 'manage-feature', + ), + ) } -const VARIATION_OBJECT_SCHEMA = { - type: 'object' as const, - description: 'A feature variation configuration', - properties: { - _id: { - type: 'string' as const, - description: 'MongoDB ID for the variation', - }, - key: VARIATION_KEY_PROPERTY, - name: { - type: 'string' as const, - }, - variables: { - type: 'object' as const, - description: 'Variable values for this variation', - }, - }, - required: ['_id', 'key', 'name'], +export async function getFeatureAuditLogHistoryHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'getFeatureAuditLogHistory', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + const { feature_key, ...auditLogOptions } = args + return await handleZodiosValidationErrors( + () => + getFeatureAuditLogHistory( + authToken, + projectKey, + feature_key, + auditLogOptions, + ), + 'getFeatureAuditLogHistory', + ) + }, + (orgId: string, projectKey: string | undefined) => + generateFeatureDashboardLink( + orgId, + projectKey, + args.feature_key, + 'audit-log', + ), + ) } -// ============================================================================= -// TOOL DEFINITIONS -// ============================================================================= - -export const featureToolDefinitions: Tool[] = [ - { - name: 'list_features', - description: - 'List features in the current project. Include dashboard link in the response.', - annotations: { - title: 'List Feature Flags', - readOnlyHint: true, - }, - inputSchema: { - type: 'object', - properties: FEATURE_PAGINATION_PROPERTIES, - }, - outputSchema: { - type: 'object' as const, - properties: { - result: { - type: 'array' as const, - description: 'Array of feature objects in the project', - items: FEATURE_OBJECT_SCHEMA, - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'create_feature', - description: - 'Create a new feature flag. Include dashboard link in the response.', - annotations: { - title: 'Create Feature Flag', - }, - inputSchema: { - type: 'object', - properties: { - key: FEATURE_KEY_PROPERTY, - name: FEATURE_NAME_PROPERTY, - description: FEATURE_DESCRIPTION_PROPERTY, - type: FEATURE_TYPE_PROPERTY, - tags: { - type: 'array', - items: { - type: 'string', - }, - description: 'Tags to organize features', - }, - controlVariation: CONTROL_VARIATION_PROPERTY, - settings: FEATURE_SETTINGS_PROPERTY, - sdkVisibility: SDK_VISIBILITY_PROPERTY, - variables: FEATURE_VARIABLES_PROPERTY, - variations: { - type: 'array', - description: 'Array of variations for this feature', - items: { - type: 'object', - description: 'Variation data with key, name, variables', - }, - }, - configurations: { - type: 'object', - description: - 'Environment-specific configurations (key-value map of environment keys to config)', - additionalProperties: { - type: 'object', - properties: { - targets: { - type: 'array', - description: - 'Targeting rules for this environment', - }, - status: FEATURE_STATUS_PROPERTY, - }, - }, - }, - }, - }, - outputSchema: { - type: 'object' as const, - properties: { - result: FEATURE_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'update_feature', - description: - 'Update an existing feature flag. ⚠️ IMPORTANT: Changes to feature flags may affect production environments. Always confirm with the user before making changes to features that are active in production. Include dashboard link in the response.', - annotations: { - title: 'Update Feature Flag', - destructiveHint: true, - }, - inputSchema: { - type: 'object', - properties: { - key: FEATURE_KEY_PROPERTY, - name: FEATURE_NAME_PROPERTY, - description: FEATURE_DESCRIPTION_PROPERTY, - type: FEATURE_TYPE_PROPERTY, - tags: { - type: 'array', - items: { - type: 'string', - }, - description: 'Tags to organize Features on the dashboard', - }, - controlVariation: CONTROL_VARIATION_PROPERTY, - settings: FEATURE_SETTINGS_PROPERTY, - sdkVisibility: SDK_VISIBILITY_PROPERTY, - variables: FEATURE_VARIABLES_PROPERTY, - variations: { - type: 'array', - description: 'Array of variations for this feature', - items: { - type: 'object', - description: - 'Variation data with key, name, variables, and _id', - }, - }, - }, - required: ['key'], - }, - outputSchema: { - type: 'object' as const, - properties: { - result: FEATURE_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, +/** + * Register feature tools with the MCP server using the new direct registration pattern + */ +export function registerFeatureTools( + serverInstance: DevCycleMCPServerInstance, + apiClient: IDevCycleApiClient, +): void { + serverInstance.registerToolWithErrorHandling( + 'list_features', + { + description: + 'List features in the current project. Include dashboard link in the response.', + annotations: { + title: 'List Feature Flags', + readOnlyHint: true, }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'update_feature_status', - description: - 'Update the status of an existing feature flag. ⚠️ IMPORTANT: Changes to feature status may affect production environments. Always confirm with the user before making changes to features that are active in production. Include dashboard link in the response.', - annotations: { - title: 'Update Feature Flag Status', - destructiveHint: true, + inputSchema: ListFeaturesArgsSchema.shape, }, - inputSchema: { - type: 'object', - properties: { - key: FEATURE_KEY_PROPERTY, - status: FEATURE_STATUS_PROPERTY, - staticVariation: { - type: 'string', - description: - 'The variation key or ID to serve if the status is set to complete (optional)', - }, - }, - required: ['key', 'status'], + async (args: any) => { + const validatedArgs = ListFeaturesArgsSchema.parse(args) + return await listFeaturesHandler(validatedArgs, apiClient) }, - outputSchema: { - type: 'object' as const, - properties: { - result: FEATURE_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'create_feature', + { + description: + 'Create a new feature flag. Include dashboard link in the response.', + annotations: { + title: 'Create Feature Flag', }, - required: ['result', 'dashboardLink'], + inputSchema: CreateFeatureArgsSchema.shape, }, - }, - { - name: 'delete_feature', - description: - 'Delete an existing feature flag. ⚠️ CRITICAL: Deleting a feature flag will remove it from ALL environments including production. ALWAYS confirm with the user before deleting any feature flag. Include dashboard link in the response.', - annotations: { - title: 'Delete Feature Flag', - destructiveHint: true, + async (args: any) => { + const validatedArgs = CreateFeatureArgsSchema.parse(args) + return await createFeatureHandler(validatedArgs, apiClient) }, - inputSchema: { - type: 'object', - properties: { - key: { - type: 'string', - description: 'The key of the feature to delete', - }, - }, - required: ['key'], - }, - outputSchema: { - type: 'object' as const, - properties: { - result: MESSAGE_RESPONSE_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'update_feature', + { + description: + 'Update an existing feature flag. ⚠️ IMPORTANT: Changes to feature flags may affect production environments. Always confirm with the user before making changes to features that are active in production. Include dashboard link in the response.', + annotations: { + title: 'Update Feature Flag', + destructiveHint: true, }, - required: ['result', 'dashboardLink'], + inputSchema: UpdateFeatureArgsSchema.shape, }, - }, - { - name: 'fetch_feature_variations', - description: - 'Get a list of variations for a feature. Include dashboard link in the response.', - annotations: { - title: 'Get Feature Variations', - readOnlyHint: true, + async (args: any) => { + const validatedArgs = UpdateFeatureArgsSchema.parse(args) + return await updateFeatureHandler(validatedArgs, apiClient) }, - inputSchema: { - type: 'object', - properties: { - feature_key: FEATURE_KEY_PROPERTY, - }, - required: ['feature_key'], - }, - outputSchema: { - type: 'object' as const, - properties: { - result: { - type: 'array' as const, - description: 'Array of variation objects for the feature', - items: VARIATION_OBJECT_SCHEMA, - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'update_feature_status', + { + description: + 'Update the status of an existing feature flag. ⚠️ IMPORTANT: Changes to feature status may affect production environments. Always confirm with the user before making changes to features that are active in production. Include dashboard link in the response.', + annotations: { + title: 'Update Feature Flag Status', + destructiveHint: true, }, - required: ['result', 'dashboardLink'], + inputSchema: UpdateFeatureStatusArgsSchema.shape, }, - }, - { - name: 'create_feature_variation', - description: - 'Create a new variation within a feature. Include dashboard link in the response.', - annotations: { - title: 'Create Feature Variation', + async (args: any) => { + const validatedArgs = UpdateFeatureStatusArgsSchema.parse(args) + return await updateFeatureStatusHandler(validatedArgs, apiClient) }, - inputSchema: { - type: 'object', - properties: { - feature_key: FEATURE_KEY_PROPERTY, - key: VARIATION_KEY_PROPERTY, - name: VARIATION_NAME_PROPERTY, - variables: { - type: 'object', - description: - 'Optional key-value map of variable keys to their values for this variation (supports string, number, boolean, and object values)', - additionalProperties: true, - }, - }, - required: ['feature_key', 'key', 'name'], - }, - outputSchema: { - type: 'object' as const, - properties: { - result: VARIATION_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'delete_feature', + { + description: + 'Delete an existing feature flag. ⚠️ CRITICAL: Deleting a feature flag will remove it from ALL environments including production. ALWAYS confirm with the user before deleting any feature flag. Include dashboard link in the response.', + annotations: { + title: 'Delete Feature Flag', + destructiveHint: true, }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'update_feature_variation', - description: - 'Update an existing variation by key. ⚠️ WARNING: Updating a feature variation may affect production environments. Include dashboard link in the response.', - annotations: { - title: 'Update Feature Variation', - destructiveHint: true, + inputSchema: DeleteFeatureArgsSchema.shape, }, - inputSchema: { - type: 'object', - properties: { - _id: { - type: 'string', - description: 'MongoDB ID for the variation', - }, - feature_key: FEATURE_KEY_PROPERTY, - variation_key: VARIATION_KEY_PROPERTY, - key: VARIATION_KEY_PROPERTY, - name: VARIATION_NAME_PROPERTY, - variables: VARIATION_VARIABLES_PROPERTY, - }, - required: ['feature_key', 'variation_key'], + async (args: any) => { + const validatedArgs = DeleteFeatureArgsSchema.parse(args) + return await deleteFeatureHandler(validatedArgs, apiClient) }, - outputSchema: { - type: 'object' as const, - properties: { - result: VARIATION_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'fetch_feature_variations', + { + description: + 'Get a list of variations for a feature. Include dashboard link in the response.', + annotations: { + title: 'Get Feature Variations', + readOnlyHint: true, }, - required: ['result', 'dashboardLink'], + inputSchema: ListVariationsArgsSchema.shape, }, - }, - { - name: 'enable_feature_targeting', - description: - 'Enable targeting for a feature in an environment. ⚠️ IMPORTANT: Always confirm with the user before making changes to production environments (environments where type = "production"). Include dashboard link in the response.', - annotations: { - title: 'Enable Feature Targeting', - destructiveHint: true, - }, - inputSchema: { - type: 'object', - properties: { - feature_key: FEATURE_KEY_PROPERTY, - environment_key: ENVIRONMENT_KEY_PROPERTY, - }, - required: ['feature_key', 'environment_key'], + async (args: any) => { + const validatedArgs = ListVariationsArgsSchema.parse(args) + return await fetchFeatureVariationsHandler(validatedArgs, apiClient) }, - outputSchema: { - type: 'object' as const, - properties: { - result: MESSAGE_RESPONSE_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'create_feature_variation', + { + description: + 'Create a new variation within a feature. Include dashboard link in the response.', + annotations: { + title: 'Create Feature Variation', }, - required: ['result', 'dashboardLink'], + inputSchema: CreateVariationArgsSchema.shape, }, - }, - { - name: 'disable_feature_targeting', - description: - 'Disable targeting for a feature in an environment. ⚠️ IMPORTANT: Always confirm with the user before making changes to production environments (environments where type = "production"). Include dashboard link in the response.', - annotations: { - title: 'Disable Feature Targeting', - destructiveHint: true, - }, - inputSchema: { - type: 'object', - properties: { - feature_key: FEATURE_KEY_PROPERTY, - environment_key: ENVIRONMENT_KEY_PROPERTY, - }, - required: ['feature_key', 'environment_key'], + async (args: any) => { + const validatedArgs = CreateVariationArgsSchema.parse(args) + return await createFeatureVariationHandler(validatedArgs, apiClient) }, - outputSchema: { - type: 'object' as const, - properties: { - result: MESSAGE_RESPONSE_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'update_feature_variation', + { + description: + 'Update an existing variation by key. ⚠️ WARNING: Updating a feature variation may affect production environments. Include dashboard link in the response.', + annotations: { + title: 'Update Feature Variation', + destructiveHint: true, }, - required: ['result', 'dashboardLink'], + inputSchema: UpdateVariationArgsSchema.shape, }, - }, - { - name: 'list_feature_targeting', - description: - 'List feature configurations (targeting rules) for a feature. Include dashboard link in the response.', - annotations: { - title: 'List Feature Targeting Rules', - readOnlyHint: true, + async (args: any) => { + const validatedArgs = UpdateVariationArgsSchema.parse(args) + return await updateFeatureVariationHandler(validatedArgs, apiClient) }, - inputSchema: { - type: 'object', - properties: { - feature_key: FEATURE_KEY_PROPERTY, - environment_key: ENVIRONMENT_KEY_OPTIONAL_PROPERTY, - }, - required: ['feature_key'], - }, - outputSchema: { - type: 'object' as const, - properties: { - result: { - type: 'object' as const, - description: 'Feature targeting configuration object', - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'set_feature_targeting', + { + description: + 'Set targeting status for a feature in an environment. ⚠️ IMPORTANT: Always confirm with the user before making changes to production environments (environments where type = "production"). Include dashboard link in the response.', + annotations: { + title: 'Set Feature Targeting', + destructiveHint: true, }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'update_feature_targeting', - description: - 'Update feature configuration (targeting rules) for a feature in an environment. ⚠️ IMPORTANT: Always confirm with the user before making changes to production environments (environments where type = "production"). Include dashboard link in the response.', - annotations: { - title: 'Update Feature Targeting Rules', - destructiveHint: true, + inputSchema: SetFeatureTargetingArgsSchema.shape, }, - inputSchema: { - type: 'object', - properties: { - feature_key: FEATURE_KEY_PROPERTY, - environment_key: ENVIRONMENT_KEY_PROPERTY, - status: { - type: 'string', - enum: ['active', 'inactive', 'archived'], - description: 'The targeting status for the feature', - }, - targets: { - type: 'array', - description: - 'Array of targeting rules/targets for the feature', - items: { - type: 'object', - properties: { - _id: { - type: 'string', - description: 'MongoDB ID for the target', - }, - name: { - type: 'string', - }, - audience: TARGET_AUDIENCE_PROPERTY, - distribution: { - type: 'array', - description: - 'Variation distribution for the target', - items: { - type: 'object', - properties: { - percentage: { - type: 'number', - minimum: 0, - maximum: 1, - description: - 'Percentage of traffic for this variation (0-1)', - }, - _variation: { - type: 'string', - description: 'Variation ID', - }, - }, - required: ['percentage', '_variation'], - }, - }, - rollout: { - type: 'object', - description: 'Rollout configuration (optional)', - properties: { - startPercentage: { - type: 'number', - minimum: 0, - maximum: 1, - description: - 'Starting percentage for rollout (optional)', - }, - type: { - type: 'string', - enum: [ - 'schedule', - 'gradual', - 'stepped', - ], - description: 'Rollout type', - }, - startDate: { - type: 'string', - format: 'date-time', - description: 'Rollout start date', - }, - stages: { - type: 'array', - description: - 'Rollout stages (optional)', - items: { - type: 'object', - properties: { - percentage: { - type: 'number', - minimum: 0, - maximum: 1, - description: - 'Target percentage for this stage', - }, - type: { - type: 'string', - enum: [ - 'linear', - 'discrete', - ], - description: 'Stage type', - }, - date: { - type: 'string', - format: 'date-time', - description: - 'Date for this stage', - }, - }, - required: [ - 'percentage', - 'type', - 'date', - ], - }, - }, - }, - required: ['type', 'startDate'], - }, - }, - required: ['audience', 'distribution'], - }, - }, - }, - required: ['feature_key', 'environment_key'], + async (args: any) => { + const validatedArgs = SetFeatureTargetingArgsSchema.parse(args) + return await setFeatureTargetingHandler(validatedArgs, apiClient) }, - outputSchema: { - type: 'object' as const, - properties: { - result: { - type: 'object' as const, - description: 'Updated feature targeting configuration', - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'list_feature_targeting', + { + description: + 'List feature configurations (targeting rules) for a feature. Include dashboard link in the response.', + annotations: { + title: 'List Feature Targeting Rules', + readOnlyHint: true, }, - required: ['result', 'dashboardLink'], + inputSchema: ListFeatureTargetingArgsSchema.shape, }, - }, - { - name: 'get_feature_audit_log_history', - description: - 'Get timeline of feature flag changes from DevCycle audit log. Include dashboard link in the response.', - annotations: { - title: 'Get Feature Audit Log History', - readOnlyHint: true, + async (args: any) => { + const validatedArgs = ListFeatureTargetingArgsSchema.parse(args) + return await listFeatureTargetingHandler(validatedArgs, apiClient) }, - inputSchema: { - type: 'object', - properties: { - feature_key: FEATURE_KEY_PROPERTY, - days_back: { - type: 'number', - description: - 'Number of days to look back (default: 30, max: 365)', - minimum: 1, - maximum: 365, - }, + ) + + serverInstance.registerToolWithErrorHandling( + 'update_feature_targeting', + { + description: + 'Update feature configuration (targeting rules) for a feature in an environment. ⚠️ IMPORTANT: Always confirm with the user before making changes to production environments (environments where type = "production"). Include dashboard link in the response.', + annotations: { + title: 'Update Feature Targeting Rules', + destructiveHint: true, }, - required: ['feature_key'], + inputSchema: UpdateFeatureTargetingArgsSchema.shape, }, - outputSchema: { - type: 'object' as const, - properties: { - result: { - type: 'array' as const, - description: 'Array of audit log entries for the feature', - items: { - type: 'object' as const, - }, - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], + async (args: any) => { + const validatedArgs = UpdateFeatureTargetingArgsSchema.parse(args) + return await updateFeatureTargetingHandler(validatedArgs, apiClient) }, - }, -] - -export const featureToolHandlers: Record = { - list_features: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = ListFeaturesArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'listFeatures', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => fetchFeatures(authToken, projectKey, validatedArgs), - 'listFeatures', - ) - }, - generateFeaturesDashboardLink, - ) - }, - create_feature: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = CreateFeatureArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'createFeature', - validatedArgs, - async (authToken, projectKey) => { - if (validatedArgs.interactive) { - throw new Error( - 'Interactive mode not yet supported in MCP. Please provide explicit parameters: key, name, description, type', - ) - } - - if (!validatedArgs.key || !validatedArgs.name) { - throw new Error( - 'Feature key and name are required when not using interactive mode', - ) - } - - // Remove the MCP-specific 'interactive' property and pass the rest to the API - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { interactive, ...featureData } = validatedArgs - - return await handleZodiosValidationErrors( - () => createFeature(authToken, projectKey, featureData), - 'createFeature', - ) - }, - (orgId, projectKey, result) => - generateFeatureDashboardLink( - orgId, - projectKey, - result.key, - 'overview', - ), - ) - }, - update_feature: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = UpdateFeatureArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'updateFeature', - validatedArgs, - async (authToken, projectKey) => { - const { key, ...updateData } = validatedArgs - - return await handleZodiosValidationErrors( - () => updateFeature(authToken, projectKey, key, updateData), - 'updateFeature', - ) - }, - (orgId, projectKey, result) => - generateFeatureDashboardLink( - orgId, - projectKey, - result.key, - 'manage-feature', - ), - ) - }, - update_feature_status: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = UpdateFeatureStatusArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'updateFeatureStatus', - validatedArgs, - async (authToken, projectKey) => { - const { key, ...statusData } = validatedArgs - - return await handleZodiosValidationErrors( - () => - updateFeatureStatus( - authToken, - projectKey, - key, - statusData, - ), - 'updateFeatureStatus', - ) - }, - (orgId, projectKey, result) => - generateFeatureDashboardLink( - orgId, - projectKey, - result.key, - 'overview', - ), - ) - }, - delete_feature: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = DeleteFeatureArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'deleteFeature', - validatedArgs, - async (authToken, projectKey) => { - await handleZodiosValidationErrors( - () => - deleteFeature(authToken, projectKey, validatedArgs.key), - 'deleteFeature', - ) - return { - message: `Feature '${validatedArgs.key}' deleted successfully`, - } - }, - generateFeaturesDashboardLink, - ) - }, - fetch_feature_variations: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = ListVariationsArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'fetchFeatureVariations', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => - fetchVariations( - authToken, - projectKey, - validatedArgs.feature_key, - ), - 'fetchVariations', - ) - }, - (orgId, projectKey) => - generateFeatureDashboardLink( - orgId, - projectKey, - validatedArgs.feature_key, - 'overview', - ), - ) - }, - create_feature_variation: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = CreateVariationArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'createFeatureVariation', - validatedArgs, - async (authToken, projectKey) => { - const { feature_key, ...variationData } = validatedArgs + ) - return await handleZodiosValidationErrors( - () => - createVariation( - authToken, - projectKey, - feature_key, - variationData, - ), - 'createVariation', - ) - }, - (orgId, projectKey, result) => - generateFeatureDashboardLink( - orgId, - projectKey, - result.key, - 'manage-feature', - ), - ) - }, - update_feature_variation: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = UpdateVariationArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'updateFeatureVariation', - validatedArgs, - async (authToken, projectKey) => { - const { feature_key, variation_key, ...variationData } = - validatedArgs - - return await handleZodiosValidationErrors( - () => - updateVariation( - authToken, - projectKey, - feature_key, - variation_key, - variationData, - ), - 'updateVariation', - ) - }, - (orgId, projectKey, result) => - generateFeatureDashboardLink( - orgId, - projectKey, - result.key, - 'manage-feature', - ), - ) - }, - enable_feature_targeting: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = EnableTargetingArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'enableTargeting', - validatedArgs, - async (authToken, projectKey) => { - await handleZodiosValidationErrors( - () => - enableTargeting( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.environment_key, - ), - 'enableTargeting', - ) - return { - message: `Targeting enabled for feature '${validatedArgs.feature_key}' in environment '${validatedArgs.environment_key}'`, - } - }, - (orgId, projectKey) => - generateFeatureDashboardLink( - orgId, - projectKey, - validatedArgs.feature_key, - 'manage-feature', - ), - ) - }, - disable_feature_targeting: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = DisableTargetingArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'disableTargeting', - validatedArgs, - async (authToken, projectKey) => { - await handleZodiosValidationErrors( - () => - disableTargeting( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.environment_key, - ), - 'disableTargeting', - ) - return { - message: `Targeting disabled for feature '${validatedArgs.feature_key}' in environment '${validatedArgs.environment_key}'`, - } - }, - (orgId, projectKey) => - generateFeatureDashboardLink( - orgId, - projectKey, - validatedArgs.feature_key, - 'manage-feature', - ), - ) - }, - list_feature_targeting: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = ListFeatureTargetingArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'listFeatureTargeting', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => - fetchTargetingForFeature( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.environment_key, - ), - 'fetchTargetingForFeature', - ) - }, - (orgId, projectKey) => - generateFeatureDashboardLink( - orgId, - projectKey, - validatedArgs.feature_key, - 'manage-feature', - ), - ) - }, - update_feature_targeting: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = UpdateFeatureTargetingArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'updateFeatureTargeting', - validatedArgs, - async (authToken, projectKey) => { - const { feature_key, environment_key, ...configData } = - validatedArgs - - return await handleZodiosValidationErrors( - () => - updateFeatureConfigForEnvironment( - authToken, - projectKey, - feature_key, - environment_key, - configData, - ), - 'updateFeatureConfigForEnvironment', - ) - }, - (orgId, projectKey) => - generateFeatureDashboardLink( - orgId, - projectKey, - validatedArgs.feature_key, - 'manage-feature', - ), - ) - }, - get_feature_audit_log_history: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = GetFeatureAuditLogHistoryArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'getFeatureAuditLogHistory', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => - getFeatureAuditLogHistory( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.days_back || 30, - ), - 'getFeatureAuditLogHistory', - ) - }, - (orgId, projectKey) => - generateFeatureDashboardLink( - orgId, - projectKey, - validatedArgs.feature_key, - 'audit-log', - ), - ) - }, + serverInstance.registerToolWithErrorHandling( + 'get_feature_audit_log_history', + { + description: + 'Get feature flag audit log history from DevCycle. Include dashboard link in the response.', + annotations: { + title: 'Get Feature Audit Log History', + readOnlyHint: true, + }, + inputSchema: GetFeatureAuditLogHistoryArgsSchema.shape, + }, + async (args: any) => { + const validatedArgs = + GetFeatureAuditLogHistoryArgsSchema.parse(args) + return await getFeatureAuditLogHistoryHandler( + validatedArgs, + apiClient, + ) + }, + ) } diff --git a/src/mcp/tools/index.ts b/src/mcp/tools/index.ts new file mode 100644 index 000000000..efc7d9d1e --- /dev/null +++ b/src/mcp/tools/index.ts @@ -0,0 +1,30 @@ +/** + * Central tool registration module for DevCycle MCP tools + */ +import { IDevCycleApiClient } from '../api/interface' +import { DevCycleMCPServerInstance } from '../server' +import { registerProjectTools } from './projectTools' +import { registerEnvironmentTools } from './environmentTools' +import { registerFeatureTools } from './featureTools' +import { registerResultsTools } from './resultsTools' +import { registerSelfTargetingTools } from './selfTargetingTools' +import { registerVariableTools } from './variableTools' + +/** + * Register all DevCycle MCP tools with a server instance + */ +export function registerAllToolsWithServer( + serverInstance: DevCycleMCPServerInstance, + apiClient: IDevCycleApiClient, +): void { + registerProjectTools(serverInstance, apiClient) + // registerCustomPropertiesTools(serverInstance, apiClient) // DISABLED: Custom properties tools + registerEnvironmentTools(serverInstance, apiClient) + registerFeatureTools(serverInstance, apiClient) + registerResultsTools(serverInstance, apiClient) + registerSelfTargetingTools(serverInstance, apiClient) + registerVariableTools(serverInstance, apiClient) +} + +export type { IDevCycleApiClient } from '../api/interface' +export type { DevCycleMCPServerInstance } from '../server' diff --git a/src/mcp/tools/projectTools.ts b/src/mcp/tools/projectTools.ts index db444c564..4625567e4 100644 --- a/src/mcp/tools/projectTools.ts +++ b/src/mcp/tools/projectTools.ts @@ -1,5 +1,5 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' +import { z } from 'zod' +import { handleZodiosValidationErrors } from '../utils/api' import { fetchProjects, fetchProject, @@ -11,14 +11,19 @@ import { CreateProjectArgsSchema, UpdateProjectArgsSchema, } from '../types' -import { ToolHandler } from '../server' -import { DASHBOARD_LINK_PROPERTY, PROJECT_KEY_PROPERTY } from './commonSchemas' +import { IDevCycleApiClient } from '../api/interface' +import { DevCycleMCPServerInstance } from '../server' // Helper functions to generate project dashboard links const generateProjectDashboardLink = ( orgId: string, - projectKey: string, + projectKey: string | undefined, ): string => { + if (!projectKey) { + throw new Error( + 'Project key is required for project dashboard link. Please select a project using the selecting a project first.', + ) + } return `https://app.devcycle.com/o/${orgId}/p/${projectKey}` } @@ -26,258 +31,159 @@ const generateOrganizationSettingsLink = (orgId: string): string => { return `https://app.devcycle.com/o/${orgId}/settings` } -const generateEditProjectLink = (orgId: string, projectKey: string): string => { +const generateEditProjectLink = ( + orgId: string, + projectKey: string | undefined, +): string => { return `https://app.devcycle.com/o/${orgId}/settings/projects/${projectKey}/edit` } -// ============================================================================= -// INPUT SCHEMAS -// ============================================================================= - -const PROJECT_COMMON_PROPERTIES = { - name: { - type: 'string' as const, - description: 'Project name', - }, - description: { - type: 'string' as const, - description: 'Project description', - }, - key: PROJECT_KEY_PROPERTY, - color: { - type: 'string' as const, - description: 'Project color (hex format)', - }, -} - -const PROJECT_PAGINATION_PROPERTIES = { - page: { - type: 'number' as const, - description: 'Page number', - minimum: 1, - default: 1, - }, - perPage: { - type: 'number' as const, - description: 'Items per page', - minimum: 1, - maximum: 1000, - default: 100, - }, - sortBy: { - type: 'string' as const, - description: 'Sort field', - enum: [ - 'createdAt', - 'updatedAt', - 'name', - 'key', - 'createdBy', - 'propertyKey', - ], - default: 'createdAt', - }, - sortOrder: { - type: 'string' as const, - description: 'Sort order', - enum: ['asc', 'desc'], - default: 'desc', - }, - search: { - type: 'string' as const, - description: 'Search query to filter results', - }, - createdBy: { - type: 'string' as const, - description: 'Filter by creator', - }, +// Individual handler functions +export async function listProjectsHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'listProjects', + args, + async (authToken: string) => { + return await handleZodiosValidationErrors( + () => fetchProjects(authToken, args), + 'fetchProjects', + ) + }, + generateOrganizationSettingsLink, + ) } -// ============================================================================= -// OUTPUT SCHEMAS -// ============================================================================= - -const PROJECT_OBJECT_SCHEMA = { - type: 'object' as const, - description: 'A DevCycle project configuration', - properties: { - _id: { - type: 'string' as const, - description: 'Unique identifier for the project', - }, - key: PROJECT_KEY_PROPERTY, - name: { - type: 'string' as const, - description: 'Display name of the project', - }, - description: { - type: 'string' as const, - description: 'Optional description of the project', - }, - color: { - type: 'string' as const, - description: 'Color used to represent this project in the UI', - }, - createdAt: { - type: 'string' as const, - description: 'ISO timestamp when the project was created', - }, - updatedAt: { - type: 'string' as const, - description: 'ISO timestamp when the project was last updated', +export async function getCurrentProjectHandler(apiClient: IDevCycleApiClient) { + return await apiClient.executeWithDashboardLink( + 'getCurrentProject', + null, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for getting current project. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => fetchProject(authToken, projectKey), + 'fetchProject', + ) }, - }, - required: ['_id', 'key', 'name', 'createdAt', 'updatedAt'], + generateProjectDashboardLink, + ) } -// Complete output schema definitions -const PROJECT_OUTPUT_SCHEMA = { - type: 'object' as const, - properties: { - result: PROJECT_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], +export async function createProjectHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'createProject', + args, + async (authToken: string) => { + return await handleZodiosValidationErrors( + () => createProject(authToken, args), + 'createProject', + ) + }, + generateProjectDashboardLink, + ) } -// ============================================================================= -// TOOL DEFINITIONS -// ============================================================================= +export async function updateProjectHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + const { key, ...updateParams } = args -export const projectToolDefinitions: Tool[] = [ - { - name: 'list_projects', - description: - 'List all projects in the current organization. Include dashboard link in the response.', - annotations: { - title: 'List Projects', - readOnlyHint: true, - }, - inputSchema: { - type: 'object', - properties: PROJECT_PAGINATION_PROPERTIES, + return await apiClient.executeWithDashboardLink( + 'updateProject', + args, + async (authToken: string) => { + return await handleZodiosValidationErrors( + () => updateProject(authToken, key, updateParams), + 'updateProject', + ) }, - outputSchema: { - type: 'object' as const, - properties: { - result: { - type: 'array' as const, - description: 'Array of project objects in the organization', - items: PROJECT_OBJECT_SCHEMA, - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, + generateEditProjectLink, + ) +} + +/** + * Register project tools with the MCP server using the new direct registration pattern + */ +export function registerProjectTools( + serverInstance: DevCycleMCPServerInstance, + apiClient: IDevCycleApiClient, +): void { + serverInstance.registerToolWithErrorHandling( + 'list_projects', + { + description: + 'List all projects in the current organization. Include dashboard link in the response.', + annotations: { + title: 'List Projects', + readOnlyHint: true, }, - required: ['result', 'dashboardLink'], + inputSchema: ListProjectsArgsSchema.shape, }, - }, - { - name: 'get_current_project', - description: - 'Get the currently selected project. Include dashboard link in the response.', - annotations: { - title: 'Get Current Project', - readOnlyHint: true, - }, - inputSchema: { - type: 'object', - properties: {}, - }, - outputSchema: PROJECT_OUTPUT_SCHEMA, - }, - { - name: 'create_project', - description: - 'Create a new project. Include dashboard link in the response.', - annotations: { - title: 'Create Project', - }, - inputSchema: { - type: 'object', - properties: PROJECT_COMMON_PROPERTIES, - required: ['name', 'key'], + async (args: any) => { + const validatedArgs = ListProjectsArgsSchema.parse(args) + + return await listProjectsHandler(validatedArgs, apiClient) }, - outputSchema: PROJECT_OUTPUT_SCHEMA, - }, - { - name: 'update_project', - description: - 'Update an existing project. Include dashboard link in the response.', - annotations: { - title: 'Update Project', + ) + + serverInstance.registerToolWithErrorHandling( + 'get_current_project', + { + description: + 'Get the currently selected project. Include dashboard link in the response.', + annotations: { + title: 'Get Current Project', + readOnlyHint: true, + }, + inputSchema: {}, // No parameters needed }, - inputSchema: { - type: 'object', - properties: PROJECT_COMMON_PROPERTIES, - required: ['key'], + async () => { + return await getCurrentProjectHandler(apiClient) }, - outputSchema: PROJECT_OUTPUT_SCHEMA, - }, -] + ) -export const projectToolHandlers: Record = { - list_projects: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = ListProjectsArgsSchema.parse(args) + // DISABLED: Project creation/update tools + // serverInstance.registerToolWithErrorHandling( + // 'create_project', + // { + // description: + // 'Create a new project. Include dashboard link in the response.', + // annotations: { + // title: 'Create Project', + // }, + // inputSchema: CreateProjectArgsSchema.shape, + // }, + // async (args: any) => { + // const validatedArgs = CreateProjectArgsSchema.parse(args) - return await apiClient.executeWithDashboardLink( - 'listProjects', - validatedArgs, - async (authToken) => { - // projectKey not used for listing all projects - return await handleZodiosValidationErrors( - () => fetchProjects(authToken, validatedArgs), - 'fetchProjects', - ) - }, - generateOrganizationSettingsLink, - ) - }, - get_current_project: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - return await apiClient.executeWithDashboardLink( - 'getCurrentProject', - null, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => fetchProject(authToken, projectKey), - 'fetchProject', - ) - }, - generateProjectDashboardLink, - ) - }, - create_project: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = CreateProjectArgsSchema.parse(args) + // return await createProjectHandler(validatedArgs, apiClient) + // }, + // ) - return await apiClient.executeWithDashboardLink( - 'createProject', - validatedArgs, - async (authToken) => { - // projectKey not used for creating projects - return await handleZodiosValidationErrors( - () => createProject(authToken, validatedArgs), - 'createProject', - ) - }, - generateProjectDashboardLink, - ) - }, - update_project: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = UpdateProjectArgsSchema.parse(args) - const { key, ...updateParams } = validatedArgs + // serverInstance.registerToolWithErrorHandling( + // 'update_project', + // { + // description: + // 'Update an existing project. Include dashboard link in the response.', + // annotations: { + // title: 'Update Project', + // }, + // inputSchema: UpdateProjectArgsSchema.shape, + // }, + // async (args: any) => { + // const validatedArgs = UpdateProjectArgsSchema.parse(args) - return await apiClient.executeWithDashboardLink( - 'updateProject', - validatedArgs, - async (authToken) => { - // projectKey not used - we use the key from validated args - return await handleZodiosValidationErrors( - () => updateProject(authToken, key, updateParams), - 'updateProject', - ) - }, - generateEditProjectLink, - ) - }, + // return await updateProjectHandler(validatedArgs, apiClient) + // }, + // ) } diff --git a/src/mcp/tools/resultsTools.ts b/src/mcp/tools/resultsTools.ts index 6e5235cf6..dc239adb6 100644 --- a/src/mcp/tools/resultsTools.ts +++ b/src/mcp/tools/resultsTools.ts @@ -1,5 +1,5 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' +import { z } from 'zod' +import { handleZodiosValidationErrors } from '../utils/api' import { fetchFeatureTotalEvaluations, fetchProjectTotalEvaluations, @@ -7,196 +7,140 @@ import { import { GetFeatureTotalEvaluationsArgsSchema, GetProjectTotalEvaluationsArgsSchema, - FeatureTotalEvaluationsQuerySchema, - ProjectTotalEvaluationsQuerySchema, } from '../types' -import { ToolHandler } from '../server' -import { - DASHBOARD_LINK_PROPERTY, - FEATURE_KEY_PROPERTY, - EVALUATION_QUERY_PROPERTIES, - EVALUATION_DATA_POINT_SCHEMA, - PROJECT_DATA_POINT_SCHEMA, -} from './commonSchemas' +import { IDevCycleApiClient } from '../api/interface' +import { DevCycleMCPServerInstance } from '../server' // Helper functions to generate dashboard links const generateFeatureAnalyticsDashboardLink = ( orgId: string, - projectKey: string, + projectKey: string | undefined, featureKey: string, ): string => { + if (!projectKey) { + throw new Error( + 'Project key is required for feature analytics dashboard link. Please select a project using the selecting a project first.', + ) + } return `https://app.devcycle.com/o/${orgId}/p/${projectKey}/features/${featureKey}/analytics` } const generateProjectAnalyticsDashboardLink = ( orgId: string, - projectKey: string, + projectKey: string | undefined, ): string => { + if (!projectKey) { + throw new Error( + 'Project key is required for project analytics dashboard link. Please select a project using the selecting a project first.', + ) + } return `https://app.devcycle.com/o/${orgId}/p/${projectKey}/analytics` } -// ============================================================================= -// INPUT SCHEMAS -// ============================================================================= - -const FEATURE_EVALUATION_QUERY_PROPERTIES = { - featureKey: FEATURE_KEY_PROPERTY, - ...EVALUATION_QUERY_PROPERTIES, -} - -const PROJECT_EVALUATION_QUERY_PROPERTIES = EVALUATION_QUERY_PROPERTIES - -// ============================================================================= -// OUTPUT SCHEMAS -// ============================================================================= +// Individual handler functions +export async function getFeatureTotalEvaluationsHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'getFeatureTotalEvaluations', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + const { featureKey, ...apiQueries } = args -const FEATURE_EVALUATIONS_OUTPUT_SCHEMA = { - type: 'object' as const, - properties: { - result: { - type: 'object' as const, - description: 'Feature evaluation data aggregated by time period', - properties: { - evaluations: { - type: 'array' as const, - description: 'Array of evaluation data points', - items: EVALUATION_DATA_POINT_SCHEMA, - }, - cached: { - type: 'boolean' as const, - description: 'Whether this result came from cache', - }, - updatedAt: { - type: 'string' as const, - format: 'date-time' as const, - description: 'When the data was last updated', - }, - }, - required: ['evaluations', 'cached', 'updatedAt'], + return await handleZodiosValidationErrors( + () => + fetchFeatureTotalEvaluations( + authToken, + projectKey, + featureKey, + apiQueries, + ), + 'fetchFeatureTotalEvaluations', + ) }, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], + (orgId, projectKey) => + generateFeatureAnalyticsDashboardLink( + orgId, + projectKey, + args.featureKey, + ), + ) } -const PROJECT_EVALUATIONS_OUTPUT_SCHEMA = { - type: 'object' as const, - properties: { - result: { - type: 'object' as const, - description: 'Project evaluation data aggregated by time period', - properties: { - evaluations: { - type: 'array' as const, - description: 'Array of evaluation data points', - items: PROJECT_DATA_POINT_SCHEMA, - }, - cached: { - type: 'boolean' as const, - description: 'Whether this result came from cache', - }, - updatedAt: { - type: 'string' as const, - format: 'date-time' as const, - description: 'When the data was last updated', - }, - }, - required: ['evaluations', 'cached', 'updatedAt'], +export async function getProjectTotalEvaluationsHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'getProjectTotalEvaluations', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => fetchProjectTotalEvaluations(authToken, projectKey, args), + 'fetchProjectTotalEvaluations', + ) }, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], + generateProjectAnalyticsDashboardLink, + ) } -// ============================================================================= -// TOOL DEFINITIONS -// ============================================================================= - -export const resultsToolDefinitions: Tool[] = [ - { - name: 'get_feature_total_evaluations', - description: - 'Get total variable evaluations per time period for a specific feature. Include dashboard link in the response.', - annotations: { - title: 'Get Feature Total Evaluations', - readOnlyHint: true, - }, - inputSchema: { - type: 'object', - properties: FEATURE_EVALUATION_QUERY_PROPERTIES, - required: ['featureKey'], - }, - outputSchema: FEATURE_EVALUATIONS_OUTPUT_SCHEMA, - }, - { - name: 'get_project_total_evaluations', - description: - 'Get total variable evaluations per time period for the entire project. Include dashboard link in the response.', - annotations: { - title: 'Get Project Total Evaluations', - readOnlyHint: true, +/** + * Register results tools with the MCP server using the new direct registration pattern + */ +export function registerResultsTools( + serverInstance: DevCycleMCPServerInstance, + apiClient: IDevCycleApiClient, +): void { + serverInstance.registerToolWithErrorHandling( + 'get_feature_total_evaluations', + { + description: + 'Get total variable evaluations per time period for a specific feature. Include dashboard link in the response.', + annotations: { + title: 'Get Feature Total Evaluations', + readOnlyHint: true, + }, + inputSchema: GetFeatureTotalEvaluationsArgsSchema.shape, }, - inputSchema: { - type: 'object', - properties: PROJECT_EVALUATION_QUERY_PROPERTIES, + async (args: any) => { + const validatedArgs = + GetFeatureTotalEvaluationsArgsSchema.parse(args) + return await getFeatureTotalEvaluationsHandler( + validatedArgs, + apiClient, + ) }, - outputSchema: PROJECT_EVALUATIONS_OUTPUT_SCHEMA, - }, -] - -export const resultsToolHandlers: Record = { - get_feature_total_evaluations: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = GetFeatureTotalEvaluationsArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'getFeatureTotalEvaluations', - validatedArgs, - async (authToken, projectKey) => { - const { featureKey, ...apiQueries } = validatedArgs - - return await handleZodiosValidationErrors( - () => - fetchFeatureTotalEvaluations( - authToken, - projectKey, - featureKey, - apiQueries, - ), - 'fetchFeatureTotalEvaluations', - ) - }, - (orgId, projectKey) => - generateFeatureAnalyticsDashboardLink( - orgId, - projectKey, - validatedArgs.featureKey, - ), - ) - }, - get_project_total_evaluations: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = GetProjectTotalEvaluationsArgsSchema.parse(args) + ) - return await apiClient.executeWithDashboardLink( - 'getProjectTotalEvaluations', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => - fetchProjectTotalEvaluations( - authToken, - projectKey, - validatedArgs, - ), - 'fetchProjectTotalEvaluations', - ) + serverInstance.registerToolWithErrorHandling( + 'get_project_total_evaluations', + { + description: + 'Get total variable evaluations per time period for the entire project. Include dashboard link in the response.', + annotations: { + title: 'Get Project Total Evaluations', + readOnlyHint: true, }, - generateProjectAnalyticsDashboardLink, - ) - }, + inputSchema: GetProjectTotalEvaluationsArgsSchema.shape, + }, + async (args: any) => { + const validatedArgs = + GetProjectTotalEvaluationsArgsSchema.parse(args) + return await getProjectTotalEvaluationsHandler( + validatedArgs, + apiClient, + ) + }, + ) } diff --git a/src/mcp/tools/selfTargetingTools.ts b/src/mcp/tools/selfTargetingTools.ts index e4ae44ec0..f13dd328c 100644 --- a/src/mcp/tools/selfTargetingTools.ts +++ b/src/mcp/tools/selfTargetingTools.ts @@ -1,5 +1,5 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' +import { z } from 'zod' +import { handleZodiosValidationErrors } from '../utils/api' import { fetchUserProfile, updateUserProfile } from '../../api/userProfile' import { fetchProjectOverridesForUser, @@ -12,333 +12,274 @@ import { SetSelfTargetingOverrideArgsSchema, ClearSelfTargetingOverridesArgsSchema, } from '../types' -import { ToolHandler } from '../server' -import { - DASHBOARD_LINK_PROPERTY, - MESSAGE_RESPONSE_SCHEMA, - FEATURE_KEY_PROPERTY, - ENVIRONMENT_KEY_PROPERTY, - VARIATION_KEY_PROPERTY, -} from './commonSchemas' +import { IDevCycleApiClient } from '../api/interface' +import { DevCycleMCPServerInstance } from '../server' // Helper functions to generate dashboard links const generateSelfTargetingDashboardLink = (orgId: string): string => { return `https://app.devcycle.com/o/${orgId}/settings/profile-overrides` } -// ============================================================================= -// INPUT SCHEMAS -// ============================================================================= - -const DVC_USER_ID_PROPERTY = { - type: 'string' as const, - description: - 'DevCycle User ID for self-targeting (use null or empty string to clear)', -} - -const OVERRIDE_COMMON_PROPERTIES = { - feature_key: FEATURE_KEY_PROPERTY, - environment_key: ENVIRONMENT_KEY_PROPERTY, - variation_key: VARIATION_KEY_PROPERTY, +// Individual handler functions +export async function getSelfTargetingIdentityHandler( + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'getSelfTargetingIdentity', + null, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => fetchUserProfile(authToken, projectKey), + 'fetchUserProfile', + ) + }, + generateSelfTargetingDashboardLink, + ) } -// ============================================================================= -// OUTPUT SCHEMAS -// ============================================================================= - -const USER_PROFILE_OBJECT_SCHEMA = { - type: 'object' as const, - description: 'DevCycle user profile for self-targeting', - properties: { - dvcUserId: { - type: 'string' as const, - description: 'DevCycle User ID for self-targeting', +export async function updateSelfTargetingIdentityHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'updateSelfTargetingIdentity', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => + updateUserProfile(authToken, projectKey, { + dvcUserId: args.dvc_user_id, + }), + 'updateUserProfile', + ) }, - }, + generateSelfTargetingDashboardLink, + ) } -const OVERRIDE_OBJECT_SCHEMA = { - type: 'object' as const, - description: 'A self-targeting override configuration', - properties: { - feature: { - type: 'string' as const, - description: 'Feature key', - }, - environment: { - type: 'string' as const, - description: 'Environment key', - }, - variation: { - type: 'string' as const, - description: 'Variation key', +export async function listSelfTargetingOverridesHandler( + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'listSelfTargetingOverrides', + null, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => fetchProjectOverridesForUser(authToken, projectKey), + 'fetchProjectOverridesForUser', + ) }, - }, - required: ['feature', 'environment', 'variation'], + generateSelfTargetingDashboardLink, + ) } -// Complete output schema definitions -const SELF_TARGETING_IDENTITY_OUTPUT_SCHEMA = { - type: 'object' as const, - properties: { - result: USER_PROFILE_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], +export async function setSelfTargetingOverrideHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'setSelfTargetingOverride', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => + updateOverride(authToken, projectKey, args.feature_key, { + environment: args.environment_key, + variation: args.variation_key, + }), + 'updateOverride', + ) + }, + generateSelfTargetingDashboardLink, + ) } -// ============================================================================= -// TOOL DEFINITIONS -// ============================================================================= +export async function clearFeatureSelfTargetingOverridesHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'clearFeatureSelfTargetingOverrides', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + await handleZodiosValidationErrors( + () => + deleteFeatureOverrides( + authToken, + projectKey, + args.feature_key, + args.environment_key, + ), + 'deleteFeatureOverrides', + ) -export const selfTargetingToolDefinitions: Tool[] = [ - { - name: 'get_self_targeting_identity', - description: - 'Get current DevCycle identity for self-targeting. Include dashboard link in the response.', - annotations: { - title: 'Get Self-Targeting Identity', - readOnlyHint: true, - }, - inputSchema: { - type: 'object', - properties: {}, + return { + message: `Cleared override for feature '${args.feature_key}' in environment '${args.environment_key}'`, + } }, - outputSchema: SELF_TARGETING_IDENTITY_OUTPUT_SCHEMA, - }, - { - name: 'update_self_targeting_identity', - description: - 'Update DevCycle identity for self-targeting and overrides. Include dashboard link in the response.', - annotations: { - title: 'Update Self-Targeting Identity', + generateSelfTargetingDashboardLink, + ) +} + +export async function clearAllSelfTargetingOverridesHandler( + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'clearAllSelfTargetingOverrides', + null, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + await handleZodiosValidationErrors( + () => deleteAllProjectOverrides(authToken, projectKey), + 'deleteAllProjectOverrides', + ) + return { message: 'Cleared all overrides for the project' } }, - inputSchema: { - type: 'object', - properties: { - dvc_user_id: DVC_USER_ID_PROPERTY, + generateSelfTargetingDashboardLink, + ) +} + +/** + * Register self-targeting tools with the MCP server using the new direct registration pattern + */ +export function registerSelfTargetingTools( + serverInstance: DevCycleMCPServerInstance, + apiClient: IDevCycleApiClient, +): void { + serverInstance.registerToolWithErrorHandling( + 'get_self_targeting_identity', + { + description: + 'Get current DevCycle identity for self-targeting. Include dashboard link in the response.', + annotations: { + title: 'Get Self-Targeting Identity', + readOnlyHint: true, }, - required: ['dvc_user_id'], - }, - outputSchema: SELF_TARGETING_IDENTITY_OUTPUT_SCHEMA, - }, - { - name: 'list_self_targeting_overrides', - description: - 'List all self-targeting overrides for the current project. Include dashboard link in the response.', - annotations: { - title: 'List Self-Targeting Overrides', - readOnlyHint: true, + inputSchema: {}, // No parameters needed }, - inputSchema: { - type: 'object', - properties: {}, + async () => { + return await getSelfTargetingIdentityHandler(apiClient) }, - outputSchema: { - type: 'object' as const, - properties: { - result: { - type: 'array' as const, - description: 'Array of self-targeting override objects', - items: OVERRIDE_OBJECT_SCHEMA, - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'update_self_targeting_identity', + { + description: + 'Update DevCycle identity for self-targeting and overrides. Include dashboard link in the response.', + annotations: { + title: 'Update Self-Targeting Identity', }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'set_self_targeting_override', - description: - 'Set a self-targeting override for a feature variation. ⚠️ IMPORTANT: Always confirm with the user before setting overrides for production environments (environments where type = "production"). Include dashboard link in the response.', - annotations: { - title: 'Set Self-Targeting Override For Feature/Environment', + inputSchema: UpdateSelfTargetingIdentityArgsSchema.shape, }, - inputSchema: { - type: 'object', - properties: OVERRIDE_COMMON_PROPERTIES, - required: ['feature_key', 'environment_key', 'variation_key'], + async (args: any) => { + const validatedArgs = + UpdateSelfTargetingIdentityArgsSchema.parse(args) + return await updateSelfTargetingIdentityHandler( + validatedArgs, + apiClient, + ) }, - outputSchema: { - type: 'object' as const, - properties: { - result: OVERRIDE_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'list_self_targeting_overrides', + { + description: + 'List all self-targeting overrides for the current project. Include dashboard link in the response.', + annotations: { + title: 'List Self-Targeting Overrides', + readOnlyHint: true, }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'clear_feature_self_targeting_overrides', - description: - 'Clear self-targeting overrides for a specific feature/environment. ⚠️ IMPORTANT: Always confirm with the user before clearing overrides for production environments (environments where type = "production"). Include dashboard link in the response.', - annotations: { - title: 'Clear Self-Targeting Override For Feature/Environment', + inputSchema: {}, // No parameters needed }, - inputSchema: { - type: 'object', - properties: { - feature_key: FEATURE_KEY_PROPERTY, - environment_key: ENVIRONMENT_KEY_PROPERTY, - }, - required: ['feature_key', 'environment_key'], + async () => { + return await listSelfTargetingOverridesHandler(apiClient) }, - outputSchema: { - type: 'object' as const, - properties: { - result: MESSAGE_RESPONSE_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'set_self_targeting_override', + { + description: + 'Set a self-targeting override for a feature variation. ⚠️ IMPORTANT: Always confirm with the user before setting overrides for production environments (environments where type = "production"). Include dashboard link in the response.', + annotations: { + title: 'Set Self-Targeting Override For Feature/Environment', }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'clear_all_self_targeting_overrides', - description: - 'Clear all self-targeting overrides for the current project. ⚠️ IMPORTANT: Always confirm with the user before clearing all overrides as it can clear production environments (environments where type = "production"). Include dashboard link in the response.', - annotations: { - title: 'Clear All Self-Targeting Overrides', - destructiveHint: true, - }, - inputSchema: { - type: 'object', - properties: {}, + inputSchema: SetSelfTargetingOverrideArgsSchema.shape, }, - outputSchema: { - type: 'object' as const, - properties: { - result: MESSAGE_RESPONSE_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], + async (args: any) => { + const validatedArgs = SetSelfTargetingOverrideArgsSchema.parse(args) + return await setSelfTargetingOverrideHandler( + validatedArgs, + apiClient, + ) }, - }, -] - -export const selfTargetingToolHandlers: Record = { - get_self_targeting_identity: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - return await apiClient.executeWithDashboardLink( - 'getSelfTargetingIdentity', - null, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => fetchUserProfile(authToken, projectKey), - 'fetchUserProfile', - ) - }, - generateSelfTargetingDashboardLink, - ) - }, - update_self_targeting_identity: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = UpdateSelfTargetingIdentityArgsSchema.parse(args) + ) - return await apiClient.executeWithDashboardLink( - 'updateSelfTargetingIdentity', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => - updateUserProfile(authToken, projectKey, { - dvcUserId: validatedArgs.dvc_user_id, - }), - 'updateUserProfile', - ) + serverInstance.registerToolWithErrorHandling( + 'clear_feature_self_targeting_overrides', + { + description: + 'Clear self-targeting overrides for a specific feature/environment. ⚠️ IMPORTANT: Always confirm with the user before clearing overrides for production environments (environments where type = "production"). Include dashboard link in the response.', + annotations: { + title: 'Clear Self-Targeting Override For Feature/Environment', }, - generateSelfTargetingDashboardLink, - ) - }, - list_self_targeting_overrides: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - return await apiClient.executeWithDashboardLink( - 'listSelfTargetingOverrides', - null, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => fetchProjectOverridesForUser(authToken, projectKey), - 'fetchProjectOverridesForUser', - ) - }, - generateSelfTargetingDashboardLink, - ) - }, - set_self_targeting_override: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = SetSelfTargetingOverrideArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'setSelfTargetingOverride', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => - updateOverride( - authToken, - projectKey, - validatedArgs.feature_key, - { - environment: validatedArgs.environment_key, - variation: validatedArgs.variation_key, - }, - ), - 'updateOverride', - ) - }, - generateSelfTargetingDashboardLink, - ) - }, - clear_feature_self_targeting_overrides: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - const validatedArgs = ClearSelfTargetingOverridesArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'clearFeatureSelfTargetingOverrides', - validatedArgs, - async (authToken, projectKey) => { - await handleZodiosValidationErrors( - () => - deleteFeatureOverrides( - authToken, - projectKey, - validatedArgs.feature_key, - validatedArgs.environment_key, - ), - 'deleteFeatureOverrides', - ) + inputSchema: ClearSelfTargetingOverridesArgsSchema.shape, + }, + async (args: any) => { + const validatedArgs = + ClearSelfTargetingOverridesArgsSchema.parse(args) + return await clearFeatureSelfTargetingOverridesHandler( + validatedArgs, + apiClient, + ) + }, + ) - return { - message: `Cleared override for feature '${validatedArgs.feature_key}' in environment '${validatedArgs.environment_key}'`, - } - }, - generateSelfTargetingDashboardLink, - ) - }, - clear_all_self_targeting_overrides: async ( - args: unknown, - apiClient: DevCycleApiClient, - ) => { - return await apiClient.executeWithDashboardLink( - 'clearAllSelfTargetingOverrides', - null, - async (authToken, projectKey) => { - await handleZodiosValidationErrors( - () => deleteAllProjectOverrides(authToken, projectKey), - 'deleteAllProjectOverrides', - ) - return { message: 'Cleared all overrides for the project' } - }, - generateSelfTargetingDashboardLink, - ) - }, + // DISABLED: Clear all self-targeting overrides tool + // serverInstance.registerToolWithErrorHandling( + // 'clear_all_self_targeting_overrides', + // { + // description: + // 'Clear all self-targeting overrides for the current project. ⚠️ IMPORTANT: Always confirm with the user before clearing all overrides as it can clear production environments (environments where type = "production"). Include dashboard link in the response.', + // annotations: { + // title: 'Clear All Self-Targeting Overrides', + // destructiveHint: true, + // }, + // inputSchema: {}, // No parameters needed + // }, + // async () => { + // return await clearAllSelfTargetingOverridesHandler(apiClient) + // }, + // ) } diff --git a/src/mcp/tools/variableTools.ts b/src/mcp/tools/variableTools.ts index 0a736cbd8..506c42d8b 100644 --- a/src/mcp/tools/variableTools.ts +++ b/src/mcp/tools/variableTools.ts @@ -1,5 +1,5 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js' -import { DevCycleApiClient, handleZodiosValidationErrors } from '../utils/api' +import { z } from 'zod' +import { handleZodiosValidationErrors } from '../utils/api' import { fetchVariables, createVariable, @@ -12,367 +12,187 @@ import { UpdateVariableArgsSchema, DeleteVariableArgsSchema, } from '../types' -import { ToolHandler } from '../server' -import { - DASHBOARD_LINK_PROPERTY, - MESSAGE_RESPONSE_SCHEMA, - VARIABLE_KEY_PROPERTY, -} from './commonSchemas' +import { IDevCycleApiClient } from '../api/interface' +import { DevCycleMCPServerInstance } from '../server' // Helper function to generate variable dashboard links const generateVariablesDashboardLink = ( orgId: string, - projectKey: string, + projectKey: string | undefined, ): string => { + if (!projectKey) { + throw new Error( + 'Project key is required for variables dashboard link. Please select a project using the selecting a project first.', + ) + } return `https://app.devcycle.com/o/${orgId}/p/${projectKey}/variables` } -// ============================================================================= -// INPUT SCHEMAS -// ============================================================================= -const VARIABLE_PAGINATION_PROPERTIES = { - page: { - type: 'number' as const, - description: 'Page number', - minimum: 1, - default: 1, - }, - perPage: { - type: 'number' as const, - description: 'Items per page', - minimum: 1, - maximum: 1000, - default: 100, - }, - sortBy: { - type: 'string' as const, - description: 'Sort field', - enum: [ - 'createdAt', - 'updatedAt', - 'name', - 'key', - 'createdBy', - 'propertyKey', - ], - default: 'createdAt', - }, - sortOrder: { - type: 'string' as const, - description: 'Sort order', - enum: ['asc', 'desc'], - default: 'desc', - }, - search: { - type: 'string' as const, - description: 'Search query to filter variables', - minLength: 3, - }, - feature: { - type: 'string' as const, - description: 'Filter by feature', - }, - type: { - type: 'string' as const, - description: 'Filter by variable type', - enum: ['String', 'Boolean', 'Number', 'JSON'], - }, - status: { - type: 'string' as const, - description: 'Filter by variable status', - enum: ['active', 'archived'], - }, -} - -const VARIABLE_TYPE_PROPERTY = { - type: 'string' as const, - enum: ['String', 'Boolean', 'Number', 'JSON'] as const, - description: 'Variable type', -} - -const VALIDATION_SCHEMA_PROPERTY = { - type: 'object' as const, - description: 'Validation schema for variable values', - properties: { - schemaType: { - type: 'string' as const, - description: 'Schema type', - }, - enumValues: { - type: 'array' as const, - description: 'Allowed enum values', - }, - regexPattern: { - type: 'string' as const, - description: 'Regex pattern for validation', - }, - jsonSchema: { - type: 'string' as const, - description: 'JSON schema for validation', - }, - description: { - type: 'string' as const, - description: 'Schema description', - }, - exampleValue: { - description: 'Example value for the schema', - }, - }, +// Individual handler functions +export async function listVariablesHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'listVariables', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => fetchVariables(authToken, projectKey, args), + 'fetchVariables', + ) + }, + generateVariablesDashboardLink, + ) } -const VARIABLE_COMMON_PROPERTIES = { - name: { - type: 'string' as const, - description: 'Variable name (1-100 characters)', - }, - description: { - type: 'string' as const, - description: 'Variable description (max 1000 characters)', - }, - key: { - type: 'string' as const, - description: - 'Unique variable key (1-100 characters, must match pattern ^[a-z0-9-_.]+$)', - }, - _feature: { - type: 'string' as const, - description: 'Feature key or ID to associate with this variable', - }, - type: VARIABLE_TYPE_PROPERTY, - defaultValue: { - description: 'Default value for the variable', - }, - validationSchema: VALIDATION_SCHEMA_PROPERTY, +export async function createVariableHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'createVariable', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => createVariable(authToken, projectKey, args), + 'createVariable', + ) + }, + generateVariablesDashboardLink, + ) } -const UPDATE_VARIABLE_PROPERTIES = { - key: { - type: 'string' as const, - description: 'Current variable key', - }, - name: VARIABLE_COMMON_PROPERTIES.name, - description: VARIABLE_COMMON_PROPERTIES.description, - type: VARIABLE_COMMON_PROPERTIES.type, - validationSchema: VARIABLE_COMMON_PROPERTIES.validationSchema, +export async function updateVariableHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + const { key, ...updateData } = args + + return await apiClient.executeWithDashboardLink( + 'updateVariable', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + return await handleZodiosValidationErrors( + () => updateVariable(authToken, projectKey, key, updateData), + 'updateVariable', + ) + }, + generateVariablesDashboardLink, + ) } -// ============================================================================= -// OUTPUT SCHEMAS -// ============================================================================= - -const VARIABLE_OBJECT_SCHEMA = { - type: 'object' as const, - description: 'A DevCycle variable configuration', - properties: { - _id: { - type: 'string' as const, - description: 'Unique identifier for the variable', - }, - key: VARIABLE_KEY_PROPERTY, - name: { - type: 'string' as const, - description: 'Display name of the variable', - }, - description: { - type: 'string' as const, - description: 'Optional description of the variable', - }, - type: { - type: 'string' as const, - description: 'Variable type (String, Boolean, Number, JSON)', - }, - defaultValue: { - description: 'Default value for the variable', - }, - _feature: { - type: 'string' as const, - description: 'Associated feature ID', - }, - validationSchema: { - type: 'object' as const, - description: 'Validation schema for the variable', - }, - createdAt: { - type: 'string' as const, - description: 'ISO timestamp when the variable was created', - }, - updatedAt: { - type: 'string' as const, - description: 'ISO timestamp when the variable was last updated', - }, - }, - required: ['_id', 'key', 'name', 'type', 'createdAt', 'updatedAt'], +export async function deleteVariableHandler( + args: z.infer, + apiClient: IDevCycleApiClient, +) { + return await apiClient.executeWithDashboardLink( + 'deleteVariable', + args, + async (authToken: string, projectKey: string | undefined) => { + if (!projectKey) { + throw new Error( + 'Project key is required for this operation. Please select a project using the selecting a project first.', + ) + } + await handleZodiosValidationErrors( + () => deleteVariable(authToken, projectKey, args.key), + 'deleteVariable', + ) + return { + message: `Variable '${args.key}' deleted successfully`, + } + }, + generateVariablesDashboardLink, + ) } -// ============================================================================= -// TOOL DEFINITIONS -// ============================================================================= - -export const variableToolDefinitions: Tool[] = [ - { - name: 'list_variables', - description: - 'List variables in the current project. Include dashboard link in the response.', - annotations: { - title: 'List Variables', - readOnlyHint: true, - }, - inputSchema: { - type: 'object', - properties: VARIABLE_PAGINATION_PROPERTIES, - }, - outputSchema: { - type: 'object' as const, - properties: { - result: { - type: 'array' as const, - description: 'Array of variable objects in the project', - items: VARIABLE_OBJECT_SCHEMA, - }, - dashboardLink: DASHBOARD_LINK_PROPERTY, +/** + * Register variable tools with the MCP server using the new direct registration pattern + */ +export function registerVariableTools( + serverInstance: DevCycleMCPServerInstance, + apiClient: IDevCycleApiClient, +): void { + serverInstance.registerToolWithErrorHandling( + 'list_variables', + { + description: + 'List variables in the current project. Include dashboard link in the response.', + annotations: { + title: 'List Variables', + readOnlyHint: true, }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'create_variable', - description: - 'Create a new variable. Include dashboard link in the response.', - annotations: { - title: 'Create Variable', + inputSchema: ListVariablesArgsSchema.shape, }, - inputSchema: { - type: 'object', - properties: VARIABLE_COMMON_PROPERTIES, - required: ['key', 'type'], + async (args: any) => { + const validatedArgs = ListVariablesArgsSchema.parse(args) + return await listVariablesHandler(validatedArgs, apiClient) }, - outputSchema: { - type: 'object' as const, - properties: { - result: VARIABLE_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + ) + + serverInstance.registerToolWithErrorHandling( + 'create_variable', + { + description: + 'Create a new variable. Include dashboard link in the response.', + annotations: { + title: 'Create Variable', }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'update_variable', - description: - 'Update an existing variable. ⚠️ IMPORTANT: Variable changes can affect feature flags in production environments. Always confirm with the user before updating variables for features that are active in production. Include dashboard link in the response.', - annotations: { - title: 'Update Variable', - destructiveHint: true, - }, - inputSchema: { - type: 'object', - properties: UPDATE_VARIABLE_PROPERTIES, - required: ['key'], - }, - outputSchema: { - type: 'object' as const, - properties: { - result: VARIABLE_OBJECT_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, + inputSchema: CreateVariableArgsSchema.shape, + }, + async (args: any) => { + const validatedArgs = CreateVariableArgsSchema.parse(args) + return await createVariableHandler(validatedArgs, apiClient) + }, + ) + + serverInstance.registerToolWithErrorHandling( + 'update_variable', + { + description: + 'Update an existing variable. ⚠️ IMPORTANT: Variable changes can affect feature flags in production environments. Always confirm with the user before updating variables for features that are active in production. Include dashboard link in the response.', + annotations: { + title: 'Update Variable', + destructiveHint: true, }, - required: ['result', 'dashboardLink'], - }, - }, - { - name: 'delete_variable', - description: - 'Delete a variable. ⚠️ CRITICAL: Deleting a variable will remove it from ALL environments including production. ALWAYS confirm with the user before deleting any variable. Include dashboard link in the response.', - annotations: { - title: 'Delete Variable', - destructiveHint: true, - }, - inputSchema: { - type: 'object', - properties: { - key: VARIABLE_KEY_PROPERTY, + inputSchema: UpdateVariableArgsSchema.shape, + }, + async (args: any) => { + const validatedArgs = UpdateVariableArgsSchema.parse(args) + return await updateVariableHandler(validatedArgs, apiClient) + }, + ) + + serverInstance.registerToolWithErrorHandling( + 'delete_variable', + { + description: + 'Delete a variable. ⚠️ CRITICAL: Deleting a variable will remove it from ALL environments including production. ALWAYS confirm with the user before deleting any variable. Include dashboard link in the response.', + annotations: { + title: 'Delete Variable', + destructiveHint: true, }, - required: ['key'], + inputSchema: DeleteVariableArgsSchema.shape, }, - outputSchema: { - type: 'object' as const, - properties: { - result: MESSAGE_RESPONSE_SCHEMA, - dashboardLink: DASHBOARD_LINK_PROPERTY, - }, - required: ['result', 'dashboardLink'], + async (args: any) => { + const validatedArgs = DeleteVariableArgsSchema.parse(args) + return await deleteVariableHandler(validatedArgs, apiClient) }, - }, -] - -export const variableToolHandlers: Record = { - list_variables: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = ListVariablesArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'listVariables', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => fetchVariables(authToken, projectKey, validatedArgs), - 'fetchVariables', - ) - }, - generateVariablesDashboardLink, - ) - }, - create_variable: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = CreateVariableArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'createVariable', - validatedArgs, - async (authToken, projectKey) => { - return await handleZodiosValidationErrors( - () => createVariable(authToken, projectKey, validatedArgs), - 'createVariable', - ) - }, - generateVariablesDashboardLink, - ) - }, - update_variable: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = UpdateVariableArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'updateVariable', - validatedArgs, - async (authToken, projectKey) => { - const { key, ...updateData } = validatedArgs - - return await handleZodiosValidationErrors( - () => - updateVariable(authToken, projectKey, key, updateData), - 'updateVariable', - ) - }, - generateVariablesDashboardLink, - ) - }, - delete_variable: async (args: unknown, apiClient: DevCycleApiClient) => { - const validatedArgs = DeleteVariableArgsSchema.parse(args) - - return await apiClient.executeWithDashboardLink( - 'deleteVariable', - validatedArgs, - async (authToken, projectKey) => { - await handleZodiosValidationErrors( - () => - deleteVariable( - authToken, - projectKey, - validatedArgs.key, - ), - 'deleteVariable', - ) - return { - message: `Variable '${validatedArgs.key}' deleted successfully`, - } - }, - generateVariablesDashboardLink, - ) - }, + ) } diff --git a/src/mcp/types.ts b/src/mcp/types.ts index 45db84e4a..f670aca54 100644 --- a/src/mcp/types.ts +++ b/src/mcp/types.ts @@ -4,8 +4,19 @@ import { UpdateFeatureStatusDto } from '../api/schemas' // Zod schemas for MCP tool arguments export const ListFeaturesArgsSchema = z.object({ - page: z.number().min(1).default(1).optional(), - perPage: z.number().min(1).max(1000).default(100).optional(), + page: z + .number() + .min(1) + .default(1) + .optional() + .describe('Page number for pagination'), + perPage: z + .number() + .min(1) + .max(1000) + .default(100) + .optional() + .describe('Number of items per page (1-1000)'), sortBy: z .enum([ 'createdAt', @@ -16,20 +27,50 @@ export const ListFeaturesArgsSchema = z.object({ 'propertyKey', ]) .default('createdAt') - .optional(), - sortOrder: z.enum(['asc', 'desc']).default('desc').optional(), - search: z.string().min(3).optional(), + .optional() + .describe('Field to sort features by'), + sortOrder: z + .enum(['asc', 'desc']) + .default('desc') + .optional() + .describe('Sort order (ascending or descending)'), + search: z + .string() + .min(3) + .optional() + .describe('Search term to filter features by name or key'), staleness: z .enum(['all', 'unused', 'released', 'unmodified', 'notStale']) - .optional(), - createdBy: z.string().optional(), - type: z.enum(['release', 'experiment', 'permission', 'ops']).optional(), - status: z.enum(['active', 'complete', 'archived']).optional(), + .optional() + .describe('Filter features by staleness status'), + createdBy: z + .string() + .optional() + .describe('Filter features by creator user ID'), + type: z + .enum(['release', 'experiment', 'permission', 'ops']) + .optional() + .describe('Filter features by type'), + status: z + .enum(['active', 'complete', 'archived']) + .optional() + .describe('Filter features by status'), }) export const ListVariablesArgsSchema = z.object({ - page: z.number().min(1).default(1).optional(), - perPage: z.number().min(1).max(1000).default(100).optional(), + page: z + .number() + .min(1) + .default(1) + .optional() + .describe('Page number for pagination'), + perPage: z + .number() + .min(1) + .max(1000) + .default(100) + .optional() + .describe('Number of items per page (1-1000)'), sortBy: z .enum([ 'createdAt', @@ -40,46 +81,156 @@ export const ListVariablesArgsSchema = z.object({ 'propertyKey', ]) .default('createdAt') - .optional(), - sortOrder: z.enum(['asc', 'desc']).default('desc').optional(), - search: z.string().min(3).optional(), - feature: z.string().optional(), - type: z.enum(['String', 'Boolean', 'Number', 'JSON']).optional(), - status: z.enum(['active', 'archived']).optional(), + .optional() + .describe('Field to sort variables by'), + sortOrder: z + .enum(['asc', 'desc']) + .default('desc') + .optional() + .describe('Sort order (ascending or descending)'), + search: z + .string() + .min(3) + .optional() + .describe('Search query to filter variables (minimum 3 characters)'), + feature: z.string().optional().describe('Filter by feature key'), + type: z + .enum(['String', 'Boolean', 'Number', 'JSON']) + .optional() + .describe('Filter by variable type'), + status: z + .enum(['active', 'archived']) + .optional() + .describe('Filter by variable status'), }) -export const CreateVariableArgsSchema = schemas.CreateVariableDto +export const CreateVariableArgsSchema = z.object({ + key: schemas.CreateVariableDto.shape.key.describe( + 'Unique variable key (1-100 characters, lowercase letters, numbers, dots, dashes, underscores only)', + ), + name: schemas.CreateVariableDto.shape.name.describe( + 'Variable name (1-100 characters)', + ), + description: schemas.CreateVariableDto.shape.description + .optional() + .describe('Variable description (max 1000 characters)'), + type: schemas.CreateVariableDto.shape.type.describe( + 'Variable type (String, Boolean, Number, JSON)', + ), + defaultValue: schemas.CreateVariableDto.shape.defaultValue + .optional() + .describe('Default value for the variable'), + _feature: schemas.CreateVariableDto.shape._feature + .optional() + .describe('Feature key or ID to associate with this variable'), + validationSchema: schemas.CreateVariableDto.shape.validationSchema + .optional() + .describe('Validation schema for variable values'), +}) -export const UpdateVariableArgsSchema = schemas.UpdateVariableDto.extend({ +export const UpdateVariableArgsSchema = z.object({ key: z .string() .max(100) - .regex(/^[a-z0-9-_.]+$/), // Make key required for identifying the variable + .regex(/^[a-z0-9-_.]+$/) + .describe('Current variable key to identify which variable to update'), + name: schemas.UpdateVariableDto.shape.name + .optional() + .describe('Variable name (1-100 characters)'), + description: schemas.UpdateVariableDto.shape.description + .optional() + .describe('Variable description (max 1000 characters)'), + type: schemas.UpdateVariableDto.shape.type + .optional() + .describe('Variable type (String, Boolean, Number, JSON)'), + validationSchema: schemas.UpdateVariableDto.shape.validationSchema + .optional() + .describe('Validation schema for variable values'), }) export const DeleteVariableArgsSchema = z.object({ - key: z.string(), + key: z + .string() + .describe('Variable key to identify which variable to delete'), }) export const DeleteFeatureArgsSchema = z.object({ - key: z.string(), + key: z.string().describe('Feature key to identify which feature to delete'), }) -export const ListProjectsArgsSchema = schemas.GetProjectsParams +export const ListProjectsArgsSchema = z.object({ + page: schemas.GetProjectsParams.shape.page.describe( + 'Page number for pagination', + ), + perPage: schemas.GetProjectsParams.shape.perPage.describe( + 'Number of items per page (1-1000)', + ), + sortBy: schemas.GetProjectsParams.shape.sortBy.describe( + 'Field to sort projects by', + ), + sortOrder: schemas.GetProjectsParams.shape.sortOrder.describe( + 'Sort order (ascending or descending)', + ), + search: schemas.GetProjectsParams.shape.search.describe( + 'Search term to filter projects by name or key', + ), + createdBy: schemas.GetProjectsParams.shape.createdBy.describe( + 'Filter projects by creator user ID', + ), +}) -export const CreateProjectArgsSchema = schemas.CreateProjectDto +export const CreateProjectArgsSchema = z.object({ + name: schemas.CreateProjectDto.shape.name.describe( + 'Project name (max 100 characters)', + ), + key: schemas.CreateProjectDto.shape.key.describe( + 'Unique project key (lowercase letters, numbers, dots, dashes, underscores only)', + ), + description: schemas.CreateProjectDto.shape.description.describe( + 'Project description (max 1000 characters)', + ), + color: schemas.CreateProjectDto.shape.color.describe( + 'Project color in hex format (e.g., #FF0000)', + ), + settings: schemas.CreateProjectDto.shape.settings.describe( + 'Project settings configuration', + ), +}) -export const UpdateProjectArgsSchema = schemas.UpdateProjectDto.extend({ +export const UpdateProjectArgsSchema = z.object({ key: z .string() .max(100) - .regex(/^[a-z0-9-_.]+$/), // Make key required for identifying the project + .regex(/^[a-z0-9-_.]+$/) + .describe('Project key to identify which project to update'), // Make key required for identifying the project + name: schemas.UpdateProjectDto.shape.name.describe( + 'Updated project name (max 100 characters)', + ), + description: schemas.UpdateProjectDto.shape.description.describe( + 'Updated project description (max 1000 characters)', + ), + color: schemas.UpdateProjectDto.shape.color.describe( + 'Updated project color in hex format (e.g., #FF0000)', + ), + settings: schemas.UpdateProjectDto.shape.settings.describe( + 'Updated project settings configuration', + ), }) export const ListEnvironmentsArgsSchema = z.object({ - search: z.string().min(3).optional(), - page: z.number().min(1).optional(), - perPage: z.number().min(1).max(1000).default(100).optional(), + search: z + .string() + .min(3) + .optional() + .describe('Search term to filter environments by name or key'), + page: z.number().min(1).optional().describe('Page number for pagination'), + perPage: z + .number() + .min(1) + .max(1000) + .default(100) + .optional() + .describe('Number of items per page (1-1000)'), sortBy: z .enum([ 'createdAt', @@ -89,120 +240,352 @@ export const ListEnvironmentsArgsSchema = z.object({ 'createdBy', 'propertyKey', ]) - .optional(), - sortOrder: z.enum(['asc', 'desc']).optional(), - createdBy: z.string().optional(), + .optional() + .describe('Field to sort environments by'), + sortOrder: z + .enum(['asc', 'desc']) + .optional() + .describe('Sort order (ascending or descending)'), + createdBy: z + .string() + .optional() + .describe('Filter environments by creator user ID'), }) export const GetSdkKeysArgsSchema = z.object({ - environmentKey: z.string(), - keyType: z.enum(['mobile', 'server', 'client']).optional(), + environmentKey: z.string().describe('Environment key to get SDK keys for'), + keyType: z + .enum(['mobile', 'server', 'client']) + .optional() + .describe('Specific type of SDK key to retrieve (optional)'), }) -export const CreateEnvironmentArgsSchema = schemas.CreateEnvironmentDto - -export const UpdateEnvironmentArgsSchema = schemas.UpdateEnvironmentDto.extend({ - key: z.string(), // Make key required for identifying the environment +export const CreateEnvironmentArgsSchema = z.object({ + name: schemas.CreateEnvironmentDto.shape.name.describe( + 'Environment name (max 100 characters)', + ), + key: schemas.CreateEnvironmentDto.shape.key.describe( + 'Unique environment key (lowercase letters, numbers, dots, dashes, underscores only)', + ), + description: schemas.CreateEnvironmentDto.shape.description.describe( + 'Environment description (max 1000 characters)', + ), + color: schemas.CreateEnvironmentDto.shape.color.describe( + 'Environment color in hex format (e.g., #FF0000)', + ), + type: schemas.CreateEnvironmentDto.shape.type.describe( + 'Environment type (development, staging, production, or disaster_recovery)', + ), + settings: schemas.CreateEnvironmentDto.shape.settings.describe( + 'Environment settings configuration', + ), }) -export const EnableTargetingArgsSchema = z.object({ - feature_key: z.string(), - environment_key: z.string(), +export const UpdateEnvironmentArgsSchema = z.object({ + key: z + .string() + .describe('Environment key to identify which environment to update'), // Make key required for identifying the environment + name: schemas.UpdateEnvironmentDto.shape.name.describe( + 'Updated environment name (max 100 characters)', + ), + description: schemas.UpdateEnvironmentDto.shape.description.describe( + 'Updated environment description (max 1000 characters)', + ), + color: schemas.UpdateEnvironmentDto.shape.color.describe( + 'Updated environment color in hex format (e.g., #FF0000)', + ), + type: schemas.UpdateEnvironmentDto.shape.type.describe( + 'Updated environment type (development, staging, production, or disaster_recovery)', + ), + settings: schemas.UpdateEnvironmentDto.shape.settings.describe( + 'Updated environment settings configuration', + ), }) -export const DisableTargetingArgsSchema = z.object({ - feature_key: z.string(), - environment_key: z.string(), +export const SetFeatureTargetingArgsSchema = z.object({ + feature_key: z.string().describe('Feature key to set targeting for'), + environment_key: z.string().describe('Environment key to set targeting in'), + enabled: z + .boolean() + .describe('Whether to enable (true) or disable (false) targeting'), }) -export const CreateFeatureArgsSchema = schemas.CreateFeatureDto.extend({ - interactive: z.boolean().optional(), // MCP-specific: prompt for missing fields +export const CreateFeatureArgsSchema = z.object({ + name: schemas.CreateFeatureDto.shape.name.describe( + 'Feature name (max 100 characters)', + ), + key: schemas.CreateFeatureDto.shape.key.describe( + 'Unique feature key (lowercase letters, numbers, dots, dashes, underscores only)', + ), + description: schemas.CreateFeatureDto.shape.description.describe( + 'Feature description (max 1000 characters)', + ), + variables: schemas.CreateFeatureDto.shape.variables.describe( + 'Array of variables to create or reassociate with this feature', + ), + configurations: schemas.CreateFeatureDto.shape.configurations.describe( + 'Environment-specific configurations (key-value map of environment keys to config)', + ), + variations: schemas.CreateFeatureDto.shape.variations.describe( + 'Array of variations for this feature', + ), + controlVariation: schemas.CreateFeatureDto.shape.controlVariation.describe( + 'The key of the variation that is used as the control variation for Metrics', + ), + settings: schemas.CreateFeatureDto.shape.settings.describe( + 'Feature-level settings configuration', + ), + sdkVisibility: schemas.CreateFeatureDto.shape.sdkVisibility.describe( + 'SDK Type Visibility Settings for mobile, client, and server SDKs', + ), + type: schemas.CreateFeatureDto.shape.type.describe( + 'Feature type (release, experiment, permission, or ops)', + ), + tags: schemas.CreateFeatureDto.shape.tags.describe( + 'Tags to organize features', + ), + interactive: z + .boolean() + .optional() + .describe('MCP-specific: prompt for missing fields'), }) -export const UpdateFeatureArgsSchema = schemas.UpdateFeatureDto.extend({ +export const UpdateFeatureArgsSchema = z.object({ key: z .string() .max(100) - .regex(/^[a-z0-9-_.]+$/), // Make key required for identifying the feature + .regex(/^[a-z0-9-_.]+$/) + .describe('Feature key to identify which feature to update'), + name: schemas.UpdateFeatureDto.shape.name.describe( + 'Updated feature name (max 100 characters)', + ), + description: schemas.UpdateFeatureDto.shape.description.describe( + 'Updated feature description (max 1000 characters)', + ), + variables: schemas.UpdateFeatureDto.shape.variables.describe( + 'Updated array of variables for this feature', + ), + variations: schemas.UpdateFeatureDto.shape.variations.describe( + 'Updated array of variations for this feature', + ), + settings: schemas.UpdateFeatureDto.shape.settings.describe( + 'Updated feature-level settings configuration', + ), + sdkVisibility: schemas.UpdateFeatureDto.shape.sdkVisibility.describe( + 'Updated SDK Type Visibility Settings for mobile, client, and server SDKs', + ), + type: schemas.UpdateFeatureDto.shape.type.describe( + 'Updated feature type (release, experiment, permission, or ops)', + ), + tags: schemas.UpdateFeatureDto.shape.tags.describe( + 'Updated tags to organize features', + ), + controlVariation: schemas.UpdateFeatureDto.shape.controlVariation.describe( + 'Updated control variation key for Metrics', + ), }) -export const UpdateFeatureStatusArgsSchema = UpdateFeatureStatusDto.extend({ +export const UpdateFeatureStatusArgsSchema = z.object({ key: z .string() .max(100) - .regex(/^[a-z0-9-_.]+$/), // Feature key for identifying the feature + .regex(/^[a-z0-9-_.]+$/) + .describe('Feature key to identify which feature to update'), + status: UpdateFeatureStatusDto.shape.status.describe( + 'Updated feature status (active, complete, or archived)', + ), + staticVariation: UpdateFeatureStatusDto.shape.staticVariation.describe( + 'The variation key or ID to serve if the status is set to complete (optional)', + ), }) export const UpdateSelfTargetingIdentityArgsSchema = z.object({ - dvc_user_id: z.string().nullable(), + dvc_user_id: z + .string() + .nullable() + .describe( + 'DevCycle User ID for self-targeting (use null or empty string to clear)', + ), }) export const SetSelfTargetingOverrideArgsSchema = z.object({ - feature_key: z.string(), - environment_key: z.string(), - variation_key: z.string(), + feature_key: z.string().describe('Feature key to set override for'), + environment_key: z.string().describe('Environment key to set override in'), + variation_key: z + .string() + .describe('Variation key to serve for the override'), }) export const ClearSelfTargetingOverridesArgsSchema = z.object({ - feature_key: z.string(), - environment_key: z.string(), + feature_key: z.string().describe('Feature key to clear overrides for'), + environment_key: z + .string() + .describe('Environment key to clear overrides in'), }) export const ListVariationsArgsSchema = z.object({ - feature_key: z.string(), -}) - -export const CreateVariationArgsSchema = schemas.CreateVariationDto.extend({ - feature_key: z.string(), // MCP-specific: identifies which feature to add variation to -}) - -export const UpdateVariationArgsSchema = - schemas.UpdateFeatureVariationDto.extend({ - feature_key: z.string(), // MCP-specific: identifies which feature the variation belongs to - variation_key: z.string(), // MCP-specific: identifies which variation to update - variables: z - .record( - z.union([ - z.string(), - z.number(), - z.boolean(), - z.record(z.unknown()), - ]), - ) - .optional(), // Constrain to API-compatible types - }) + feature_key: z.string().describe('Feature key to list variations for'), +}) + +export const CreateVariationArgsSchema = z.object({ + feature_key: z.string().describe('Feature key to create variation for'), + key: schemas.CreateVariationDto.shape.key.describe( + 'Unique variation key (lowercase letters, numbers, dots, dashes, underscores only)', + ), + name: schemas.CreateVariationDto.shape.name.describe( + 'Variation name (max 100 characters)', + ), + variables: schemas.CreateVariationDto.shape.variables.describe( + 'Key-value map of variable keys to their values for this variation', + ), +}) + +export const UpdateVariationArgsSchema = z.object({ + feature_key: z + .string() + .describe('Feature key that the variation belongs to'), + variation_key: z + .string() + .describe('Variation key to identify which variation to update'), + key: schemas.UpdateFeatureVariationDto.shape.key.describe( + 'Updated variation key (lowercase letters, numbers, dots, dashes, underscores only)', + ), + name: schemas.UpdateFeatureVariationDto.shape.name.describe( + 'Updated variation name (max 100 characters)', + ), + variables: z + .record( + z.union([ + z.string(), + z.number(), + z.boolean(), + z.record(z.unknown()), + ]), + ) + .optional() + .describe( + 'Updated key-value map of variable keys to their values for this variation', + ), +}) export const ListFeatureTargetingArgsSchema = z.object({ - feature_key: z.string(), - environment_key: z.string().optional(), + feature_key: z.string().describe('Feature key to list targeting for'), + environment_key: z + .string() + .optional() + .describe('Optional environment key to filter targeting by'), }) -export const UpdateFeatureTargetingArgsSchema = - schemas.UpdateFeatureConfigDto.extend({ - feature_key: z.string(), // MCP-specific: identifies which feature to update targeting for - environment_key: z.string(), // MCP-specific: identifies which environment to update targeting in - }) +export const UpdateFeatureTargetingArgsSchema = z.object({ + feature_key: z.string().describe('Feature key to update targeting for'), + environment_key: z + .string() + .describe('Environment key to update targeting in'), + status: schemas.UpdateFeatureConfigDto.shape.status.describe( + 'Updated targeting status for the feature', + ), + targets: schemas.UpdateFeatureConfigDto.shape.targets.describe( + 'Updated array of targeting rules/targets for the feature', + ), +}) export const GetFeatureAuditLogHistoryArgsSchema = z.object({ - feature_key: z.string(), - days_back: z.number().min(1).max(365).default(30).optional(), + feature_key: z + .string() + .describe('Feature key to get audit log history for'), + page: z + .number() + .min(1) + .default(1) + .optional() + .describe('Page number for pagination (default: 1)'), + perPage: z + .number() + .min(1) + .max(1000) + .default(100) + .optional() + .describe('Number of items per page (default: 100, max: 1000)'), + sortBy: z + .enum(['createdAt', 'updatedAt', 'action', 'user']) + .default('createdAt') + .optional() + .describe('Field to sort audit entries by (default: createdAt)'), + sortOrder: z + .enum(['asc', 'desc']) + .default('desc') + .optional() + .describe('Sort order (default: desc)'), + startDate: z + .string() + .optional() + .describe('Start date for filtering audit entries (ISO 8601 format)'), + endDate: z + .string() + .optional() + .describe('End date for filtering audit entries (ISO 8601 format)'), + environment: z + .string() + .optional() + .describe('Environment key to filter audit entries by'), + user: z.string().optional().describe('User ID to filter audit entries by'), + action: z + .string() + .optional() + .describe('Action type to filter audit entries by'), +}) + +// Zod schema for DevCycle Audit Log Entity - matches the actual swagger specification +export const AuditLogEntitySchema = z.object({ + date: z + .string() + .describe( + 'Timestamp when the audit entry was created (ISO 8601 format)', + ), + a0_user: z.string().describe('Auth0 user ID who performed the action'), + changes: z + .array(z.record(z.unknown())) + .describe('Array of changes made (objects with unknown structure)'), }) // Base evaluation query schema (matches API camelCase naming) const BaseEvaluationQuerySchema = z.object({ - startDate: z.number().optional(), - endDate: z.number().optional(), - environment: z.string().optional(), - period: z.enum(['day', 'hour', 'month']).optional(), - sdkType: z.enum(['client', 'server', 'mobile', 'api']).optional(), + startDate: z + .number() + .optional() + .describe('Start date as Unix timestamp (optional)'), + endDate: z + .number() + .optional() + .describe('End date as Unix timestamp (optional)'), + environment: z + .string() + .optional() + .describe('Environment key to filter by (optional)'), + period: z + .enum(['day', 'hour', 'month']) + .optional() + .describe('Time period for aggregation (optional)'), + sdkType: z + .enum(['client', 'server', 'mobile', 'api']) + .optional() + .describe('SDK type to filter by (optional)'), }) // MCP argument schemas (using camelCase to match API) export const GetFeatureTotalEvaluationsArgsSchema = BaseEvaluationQuerySchema.extend({ - featureKey: z.string(), - platform: z.string().optional(), - variable: z.string().optional(), + featureKey: z + .string() + .describe('Feature key to get evaluation data for'), + platform: z + .string() + .optional() + .describe('Platform to filter by (optional)'), + variable: z + .string() + .optional() + .describe('Variable key to filter by (optional)'), }) export const GetProjectTotalEvaluationsArgsSchema = BaseEvaluationQuerySchema @@ -214,8 +597,19 @@ export const ProjectTotalEvaluationsQuerySchema = GetProjectTotalEvaluationsArgsSchema export const ListCustomPropertiesArgsSchema = z.object({ - page: z.number().min(1).default(1).optional(), - perPage: z.number().min(1).max(1000).default(100).optional(), + page: z + .number() + .min(1) + .default(1) + .optional() + .describe('Page number for pagination'), + perPage: z + .number() + .min(1) + .max(1000) + .default(100) + .optional() + .describe('Number of items per page (1-1000)'), sortBy: z .enum([ 'createdAt', @@ -226,22 +620,58 @@ export const ListCustomPropertiesArgsSchema = z.object({ 'propertyKey', ]) .default('createdAt') - .optional(), - sortOrder: z.enum(['asc', 'desc']).default('desc').optional(), - search: z.string().min(3).optional(), - createdBy: z.string().optional(), + .optional() + .describe('Field to sort custom properties by'), + sortOrder: z + .enum(['asc', 'desc']) + .default('desc') + .optional() + .describe('Sort order (ascending or descending)'), + search: z + .string() + .min(3) + .optional() + .describe('Search term to filter custom properties by name or key'), + createdBy: z + .string() + .optional() + .describe('Filter custom properties by creator user ID'), }) -export const UpsertCustomPropertyArgsSchema = schemas.CreateCustomPropertyDto +export const UpsertCustomPropertyArgsSchema = z.object({ + name: schemas.CreateCustomPropertyDto.shape.name.describe( + 'Custom property name (max 100 characters)', + ), + key: schemas.CreateCustomPropertyDto.shape.key.describe( + 'Unique custom property key (lowercase letters, numbers, dots, dashes, underscores only)', + ), + type: schemas.CreateCustomPropertyDto.shape.type.describe( + 'Custom property type (String, Boolean, or Number)', + ), + propertyKey: schemas.CreateCustomPropertyDto.shape.propertyKey.describe( + 'Property key to associate with the custom property', + ), +}) -export const UpdateCustomPropertyArgsSchema = - schemas.UpdateCustomPropertyDto.extend({ - key: z - .string() - .max(100) - .regex(/^[a-z0-9-_.]+$/), // Make key required for identifying the custom property - }) +export const UpdateCustomPropertyArgsSchema = z.object({ + key: z + .string() + .max(100) + .regex(/^[a-z0-9-_.]+$/) + .describe('Custom property key to identify which property to update'), // Make key required for identifying the custom property + name: schemas.UpdateCustomPropertyDto.shape.name.describe( + 'Updated custom property name (max 100 characters)', + ), + propertyKey: schemas.UpdateCustomPropertyDto.shape.propertyKey.describe( + 'Updated property key to associate with the custom property', + ), + type: schemas.UpdateCustomPropertyDto.shape.type.describe( + 'Updated custom property type (String, Boolean, or Number)', + ), +}) export const DeleteCustomPropertyArgsSchema = z.object({ - key: z.string(), + key: z + .string() + .describe('Custom property key to identify which property to delete'), }) diff --git a/src/mcp/utils/api.ts b/src/mcp/utils/api.ts index 75d5888f7..5f08df695 100644 --- a/src/mcp/utils/api.ts +++ b/src/mcp/utils/api.ts @@ -1,5 +1,6 @@ import { DevCycleAuth } from './auth' import { setMCPToolCommand } from './headers' +import { IDevCycleApiClient } from '../api/interface' /** * Utility function to handle Zodios validation errors by extracting response data @@ -79,7 +80,7 @@ function ensureError(error: unknown): Error { return new Error(String(error)) } -export class DevCycleApiClient { +export class DevCycleApiClient implements IDevCycleApiClient { constructor(private auth: DevCycleAuth) {} /** @@ -88,7 +89,10 @@ export class DevCycleApiClient { public async executeWithLogging( operationName: string, args: any, - operation: (authToken: string, projectKey: string) => Promise, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, requiresProject = true, ): Promise { console.error( @@ -106,7 +110,9 @@ export class DevCycleApiClient { setMCPToolCommand(operationName) const authToken = this.auth.getAuthToken() - const projectKey = requiresProject ? this.auth.getProjectKey() : '' + const projectKey = requiresProject + ? this.auth.getProjectKey() + : undefined return await operation(authToken, projectKey) } catch (error) { console.error( @@ -123,8 +129,15 @@ export class DevCycleApiClient { public async executeWithDashboardLink( operationName: string, args: any, - operation: (authToken: string, projectKey: string) => Promise, - dashboardLink: (orgId: string, projectKey: string, result: T) => string, + operation: ( + authToken: string, + projectKey: string | undefined, + ) => Promise, + dashboardLink: ( + orgId: string, + projectKey: string | undefined, + result: T, + ) => string, ): Promise<{ result: T; dashboardLink: string }> { const result = await this.executeWithLogging( operationName, diff --git a/src/mcp/utils/errorHandling.ts b/src/mcp/utils/errorHandling.ts new file mode 100644 index 000000000..8d89214ad --- /dev/null +++ b/src/mcp/utils/errorHandling.ts @@ -0,0 +1,141 @@ +/** + * Shared error handling utilities for MCP servers + */ + +export function handleToolError(error: unknown, toolName: string) { + console.error(`Error in tool handler ${toolName}:`, error) + + let errorMessage = 'Unknown error' + let errorType = 'UNKNOWN_ERROR' + let suggestions: string[] = [] + + if (error instanceof Error) { + errorMessage = error.message + errorType = categorizeError(error.message) + suggestions = getErrorSuggestions(errorType) + } else if (error && typeof error === 'string') { + errorMessage = error + } else if (error && typeof error === 'object') { + errorMessage = JSON.stringify(error) + } + + const errorResponse = { + error: true, + type: errorType, + message: errorMessage, + tool: toolName, + suggestions, + timestamp: new Date().toISOString(), + } + + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(errorResponse, null, 2), + }, + ], + } +} + +export function categorizeError(errorMessage: string): string { + const lowerMessage = errorMessage.toLowerCase() + + switch (true) { + case lowerMessage.includes('zodios: invalid response') || + lowerMessage.includes('invalid_type') || + lowerMessage.includes('expected object, received'): + return 'SCHEMA_VALIDATION_ERROR' + + case lowerMessage.includes('401') || + lowerMessage.includes('unauthorized'): + return 'AUTHENTICATION_ERROR' + + case lowerMessage.includes('403') || lowerMessage.includes('forbidden'): + return 'PERMISSION_ERROR' + + case lowerMessage.includes('project') && + lowerMessage.includes('not found'): + return 'PROJECT_ERROR' + + case lowerMessage.includes('404') || lowerMessage.includes('not found'): + return 'RESOURCE_NOT_FOUND' + + case lowerMessage.includes('400') || + lowerMessage.includes('bad request'): + return 'VALIDATION_ERROR' + + case lowerMessage.includes('429') || + lowerMessage.includes('rate limit'): + return 'RATE_LIMIT_ERROR' + + case lowerMessage.includes('enotfound') || + lowerMessage.includes('network'): + return 'NETWORK_ERROR' + + default: + return 'UNKNOWN_ERROR' + } +} + +export function getErrorSuggestions(errorType: string): string[] { + switch (errorType) { + case 'SCHEMA_VALIDATION_ERROR': + return [ + 'The API response format has changed or is unexpected', + 'This may be a temporary API issue - try again in a moment', + 'Contact DevCycle support if the issue persists', + ] + + case 'AUTHENTICATION_ERROR': + return [ + 'Re-authenticate with DevCycle (run "dvc login sso" for CLI for local MCP or re-login through OAuth for remote MCP)', + 'Verify your API credentials are correct', + 'Check if your token has expired', + ] + + case 'PERMISSION_ERROR': + return [ + 'Verify your account has permissions for this operation', + 'Check if you have access to the selected project', + 'Contact your DevCycle admin for permissions', + ] + + case 'RESOURCE_NOT_FOUND': + return [ + 'Verify the resource key/ID is correct', + 'Check if the resource exists in the selected project', + "Ensure you're in the correct environment", + ] + + case 'VALIDATION_ERROR': + return [ + 'Check the provided parameters are valid', + 'Verify required fields are not missing', + 'Review parameter format and constraints', + ] + + case 'RATE_LIMIT_ERROR': + return [ + 'Wait a moment before trying again', + 'Consider reducing the frequency of requests', + ] + + case 'NETWORK_ERROR': + return [ + 'Check your internet connection', + 'Verify firewall settings allow DevCycle API access', + 'Try again in a few moments', + ] + + case 'PROJECT_ERROR': + return [ + 'Select a valid project (use "dvc projects select" in CLI or project selection tools in workers)', + 'Verify the project key is correct', + 'Check if you have access to this project', + ] + + default: + return [] + } +} diff --git a/yarn.lock b/yarn.lock index bbdc4123b..aefdd1951 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,59 @@ __metadata: version: 8 cacheKey: 10c0 +"@ai-sdk/provider-utils@npm:2.2.8": + version: 2.2.8 + resolution: "@ai-sdk/provider-utils@npm:2.2.8" + dependencies: + "@ai-sdk/provider": "npm:1.1.3" + nanoid: "npm:^3.3.8" + secure-json-parse: "npm:^2.7.0" + peerDependencies: + zod: ^3.23.8 + checksum: 10c0/34c72bf5f23f2d3e7aef496da7099422ba3b3ff243c35511853e16c3f1528717500262eea32b19e3e09bc4452152a5f31e650512f53f08a5f5645d907bff429e + languageName: node + linkType: hard + +"@ai-sdk/provider@npm:1.1.3": + version: 1.1.3 + resolution: "@ai-sdk/provider@npm:1.1.3" + dependencies: + json-schema: "npm:^0.4.0" + checksum: 10c0/40e080e223328e7c89829865e9c48f4ce8442a6a59f7ed5dfbdb4f63e8d859a76641e2d31e91970dd389bddb910f32ec7c3dbb0ce583c119e5a1e614ea7b8bc4 + languageName: node + linkType: hard + +"@ai-sdk/react@npm:1.2.12": + version: 1.2.12 + resolution: "@ai-sdk/react@npm:1.2.12" + dependencies: + "@ai-sdk/provider-utils": "npm:2.2.8" + "@ai-sdk/ui-utils": "npm:1.2.11" + swr: "npm:^2.2.5" + throttleit: "npm:2.1.0" + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + checksum: 10c0/5422feb4ffeebd3287441cf658733e9ad7f9081fc279e85f57700d7fe9f4ed8a0504789c1be695790df44b28730e525cf12acf0f52bfa5adecc561ffd00cb2a5 + languageName: node + linkType: hard + +"@ai-sdk/ui-utils@npm:1.2.11": + version: 1.2.11 + resolution: "@ai-sdk/ui-utils@npm:1.2.11" + dependencies: + "@ai-sdk/provider": "npm:1.1.3" + "@ai-sdk/provider-utils": "npm:2.2.8" + zod-to-json-schema: "npm:^3.24.1" + peerDependencies: + zod: ^3.23.8 + checksum: 10c0/de0a10f9e16010126a21a1690aaf56d545b9c0f8d8b2cc33ffd22c2bb2e914949acb9b3f86e0e39a0e4b0d4f24db12e2b094045e34b311de0c8f84bfab48cc92 + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.2.0": version: 2.3.0 resolution: "@ampproject/remapping@npm:2.3.0" @@ -570,7 +623,80 @@ __metadata: languageName: node linkType: hard -"@cspotcode/source-map-support@npm:^0.8.0": +"@cloudflare/kv-asset-handler@npm:0.4.0": + version: 0.4.0 + resolution: "@cloudflare/kv-asset-handler@npm:0.4.0" + dependencies: + mime: "npm:^3.0.0" + checksum: 10c0/54273c796d9815294599d7958a1a4e342f5519a03cc24c9501cf24d8721de9dbb8c53262941acb0e058bd9e952f807e3e1caa3ae242a0eabc26b1d2caa9a26f6 + languageName: node + linkType: hard + +"@cloudflare/unenv-preset@npm:2.5.0": + version: 2.5.0 + resolution: "@cloudflare/unenv-preset@npm:2.5.0" + peerDependencies: + unenv: 2.0.0-rc.19 + workerd: ^1.20250722.0 + peerDependenciesMeta: + workerd: + optional: true + checksum: 10c0/3e49fac5fbedd665cc4eafc0f46993c2cb4f7ef5a1be6d71dd26d524bbc4c6a2dfc66beed058d19729a1304b76c0ed856cd48a49ee7c365544a83566a7d91a92 + languageName: node + linkType: hard + +"@cloudflare/workerd-darwin-64@npm:1.20250730.0": + version: 1.20250730.0 + resolution: "@cloudflare/workerd-darwin-64@npm:1.20250730.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@cloudflare/workerd-darwin-arm64@npm:1.20250730.0": + version: 1.20250730.0 + resolution: "@cloudflare/workerd-darwin-arm64@npm:1.20250730.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@cloudflare/workerd-linux-64@npm:1.20250730.0": + version: 1.20250730.0 + resolution: "@cloudflare/workerd-linux-64@npm:1.20250730.0" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@cloudflare/workerd-linux-arm64@npm:1.20250730.0": + version: 1.20250730.0 + resolution: "@cloudflare/workerd-linux-arm64@npm:1.20250730.0" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@cloudflare/workerd-windows-64@npm:1.20250730.0": + version: 1.20250730.0 + resolution: "@cloudflare/workerd-windows-64@npm:1.20250730.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@cloudflare/workers-oauth-provider@npm:^0.0.5": + version: 0.0.5 + resolution: "@cloudflare/workers-oauth-provider@npm:0.0.5" + dependencies: + "@cloudflare/workers-types": "npm:^4.20250311.0" + checksum: 10c0/d1d9e57c4570952235e892baeef60318dea57bfa2109acc9ecea94cdc5aef0a65ef58382173cae10811e2eca8b3a8fdd906001f3b89a393059f61c6a30226915 + languageName: node + linkType: hard + +"@cloudflare/workers-types@npm:^4.20250311.0": + version: 4.20250731.0 + resolution: "@cloudflare/workers-types@npm:4.20250731.0" + checksum: 10c0/62b818cfa236350ecfe0de03a79e288991f8079fdfa9b12cb48f1aab276be07932b0cfc58248359fd390b47d085b52b4e6d3a099c77acb852011559e1410df7b + languageName: node + linkType: hard + +"@cspotcode/source-map-support@npm:0.8.1, @cspotcode/source-map-support@npm:^0.8.0": version: 0.8.1 resolution: "@cspotcode/source-map-support@npm:0.8.1" dependencies: @@ -639,13 +765,210 @@ __metadata: ts-node: "npm:^10.9.2" typescript: "npm:^5.7.2" typescript-eslint: "npm:^8.21.0" - zod: "npm:^3.24.2" + zod: "npm:3.24.1" bin: dvc: ./bin/run dvc-mcp: ./bin/mcp languageName: unknown linkType: soft +"@devcycle/mcp-worker@workspace:mcp-worker": + version: 0.0.0-use.local + resolution: "@devcycle/mcp-worker@workspace:mcp-worker" + dependencies: + "@cloudflare/workers-oauth-provider": "npm:^0.0.5" + agents: "npm:^0.0.100" + hono: "npm:^4.8.4" + jose: "npm:^6.0.11" + oauth4webapi: "npm:^3.5.5" + wrangler: "npm:^4.22.0" + languageName: unknown + linkType: soft + +"@emnapi/runtime@npm:^1.2.0": + version: 1.4.5 + resolution: "@emnapi/runtime@npm:1.4.5" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/37a0278be5ac81e918efe36f1449875cbafba947039c53c65a1f8fc238001b866446fc66041513b286baaff5d6f9bec667f5164b3ca481373a8d9cb65bfc984b + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/aix-ppc64@npm:0.25.4" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-arm64@npm:0.25.4" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-arm@npm:0.25.4" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/android-x64@npm:0.25.4" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/darwin-arm64@npm:0.25.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/darwin-x64@npm:0.25.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/freebsd-arm64@npm:0.25.4" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/freebsd-x64@npm:0.25.4" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-arm64@npm:0.25.4" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-arm@npm:0.25.4" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-ia32@npm:0.25.4" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-loong64@npm:0.25.4" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-mips64el@npm:0.25.4" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-ppc64@npm:0.25.4" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-riscv64@npm:0.25.4" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-s390x@npm:0.25.4" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/linux-x64@npm:0.25.4" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/netbsd-arm64@npm:0.25.4" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/netbsd-x64@npm:0.25.4" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/openbsd-arm64@npm:0.25.4" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/openbsd-x64@npm:0.25.4" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/sunos-x64@npm:0.25.4" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-arm64@npm:0.25.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-ia32@npm:0.25.4" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.25.4": + version: 0.25.4 + resolution: "@esbuild/win32-x64@npm:0.25.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.7.0": version: 4.7.0 resolution: "@eslint-community/eslint-utils@npm:4.7.0" @@ -777,6 +1100,181 @@ __metadata: languageName: node linkType: hard +"@img/sharp-darwin-arm64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-darwin-arm64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-darwin-x64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.0.5": + version: 1.0.5 + resolution: "@img/sharp-libvips-linux-arm@npm:1.0.5" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linux-x64@npm:1.0.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linux-arm64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linux-arm@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.0.5" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linux-s390x@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linux-x64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linuxmusl-x64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-wasm32@npm:0.33.5" + dependencies: + "@emnapi/runtime": "npm:^1.2.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-win32-ia32@npm:0.33.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-win32-x64@npm:0.33.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -995,6 +1493,26 @@ __metadata: languageName: node linkType: hard +"@modelcontextprotocol/sdk@npm:^1.13.1": + version: 1.17.1 + resolution: "@modelcontextprotocol/sdk@npm:1.17.1" + dependencies: + ajv: "npm:^6.12.6" + content-type: "npm:^1.0.5" + cors: "npm:^2.8.5" + cross-spawn: "npm:^7.0.5" + eventsource: "npm:^3.0.2" + eventsource-parser: "npm:^3.0.0" + express: "npm:^5.0.1" + express-rate-limit: "npm:^7.5.0" + pkce-challenge: "npm:^5.0.0" + raw-body: "npm:^3.0.0" + zod: "npm:^3.23.8" + zod-to-json-schema: "npm:^3.24.1" + checksum: 10c0/bddee1c4a90adb2ee3f89f5598f0499841b1ad8b4d9a52b2b0afac3009918570d72240588a17178e319bdfbe70aced190f66b25e14f0a0645eacaf4776d4c15a + languageName: node + linkType: hard + "@modelcontextprotocol/sdk@npm:^1.15.0": version: 1.15.0 resolution: "@modelcontextprotocol/sdk@npm:1.15.0" @@ -1556,6 +2074,13 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/api@npm:1.9.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 10c0/9aae2fe6e8a3a3eeb6c1fdef78e1939cf05a0f37f8a4fae4d6bf2e09eb1e06f966ece85805626e01ba5fab48072b94f19b835449e58b6d26720ee19a58298add + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -1563,6 +2088,33 @@ __metadata: languageName: node linkType: hard +"@poppinss/colors@npm:^4.1.5": + version: 4.1.5 + resolution: "@poppinss/colors@npm:4.1.5" + dependencies: + kleur: "npm:^4.1.5" + checksum: 10c0/e9d5c9e9a8c1eb7cd37e9b431bda7b7573476be7395123b26815d758f38ca93db0ba62bb2831281f4f04a6e1620b4d4f9377268d32c61c2c9f6599a02c2112b7 + languageName: node + linkType: hard + +"@poppinss/dumper@npm:^0.6.4": + version: 0.6.4 + resolution: "@poppinss/dumper@npm:0.6.4" + dependencies: + "@poppinss/colors": "npm:^4.1.5" + "@sindresorhus/is": "npm:^7.0.2" + supports-color: "npm:^10.0.0" + checksum: 10c0/1eb1716af39fae7cc294f8fd60d10527c26bd82cd9e6438ffc71face9d6a198a3af713e7f73ef4800b1a19b99003467023ec2c5d308c38d3ba958a12d0a91989 + languageName: node + linkType: hard + +"@poppinss/exception@npm:^1.2.2": + version: 1.2.2 + resolution: "@poppinss/exception@npm:1.2.2" + checksum: 10c0/b14d1e3d532230f58238231fce6ba3bf51dab50d4ec015f3192f04320be1d692eadd831a8d437e2fc345787c96350104149fd9920264362bca143d04ec03bc4e + languageName: node + linkType: hard + "@sigstore/bundle@npm:^1.1.0": version: 1.1.0 resolution: "@sigstore/bundle@npm:1.1.0" @@ -1614,6 +2166,13 @@ __metadata: languageName: node linkType: hard +"@sindresorhus/is@npm:^7.0.2": + version: 7.0.2 + resolution: "@sindresorhus/is@npm:7.0.2" + checksum: 10c0/50881c9b651e189972087de9104e0d259a2a0dc93c604e863b3be1847e31c3dce685e76a41c0ae92198ae02b36d30d07b723a2d72015ce3cf910afc6dc337ff5 + languageName: node + linkType: hard + "@sinonjs/commons@npm:^3.0.1": version: 3.0.1 resolution: "@sinonjs/commons@npm:3.0.1" @@ -1650,6 +2209,13 @@ __metadata: languageName: node linkType: hard +"@speed-highlight/core@npm:^1.2.7": + version: 1.2.7 + resolution: "@speed-highlight/core@npm:1.2.7" + checksum: 10c0/33905da58b7e0f0857f3ec7c60a4d2e7bd7e25573dd8676de2dab555057e9873084fd2bb1d97c4629131a990f7e230cb7068045370a15c77c4412527776791d4 + languageName: node + linkType: hard + "@szmarczak/http-timer@npm:^4.0.5": version: 4.0.6 resolution: "@szmarczak/http-timer@npm:4.0.6" @@ -1762,6 +2328,13 @@ __metadata: languageName: node linkType: hard +"@types/diff-match-patch@npm:^1.0.36": + version: 1.0.36 + resolution: "@types/diff-match-patch@npm:1.0.36" + checksum: 10c0/0bad011ab138baa8bde94e7815064bb881f010452463272644ddbbb0590659cb93f7aa2776ff442c6721d70f202839e1053f8aa62d801cc4166f7a3ea9130055 + languageName: node + linkType: hard + "@types/estraverse@npm:^5.1.7": version: 5.1.7 resolution: "@types/estraverse@npm:5.1.7" @@ -2214,13 +2787,22 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.1.1": +"acorn-walk@npm:8.3.2, acorn-walk@npm:^8.1.1": version: 8.3.2 resolution: "acorn-walk@npm:8.3.2" checksum: 10c0/7e2a8dad5480df7f872569b9dccff2f3da7e65f5353686b1d6032ab9f4ddf6e3a2cb83a9b52cf50b1497fd522154dda92f0abf7153290cc79cd14721ff121e52 languageName: node linkType: hard +"acorn@npm:8.14.0": + version: 8.14.0 + resolution: "acorn@npm:8.14.0" + bin: + acorn: bin/acorn + checksum: 10c0/6d4ee461a7734b2f48836ee0fbb752903606e576cc100eb49340295129ca0b452f3ba91ddd4424a1d4406a98adfb2ebb6bd0ff4c49d7a0930c10e462719bbfd7 + languageName: node + linkType: hard + "acorn@npm:^8.15.0": version: 8.15.0 resolution: "acorn@npm:8.15.0" @@ -2266,6 +2848,23 @@ __metadata: languageName: node linkType: hard +"agents@npm:^0.0.100": + version: 0.0.100 + resolution: "agents@npm:0.0.100" + dependencies: + "@modelcontextprotocol/sdk": "npm:^1.13.1" + ai: "npm:^4.3.16" + cron-schedule: "npm:^5.0.4" + nanoid: "npm:^5.1.5" + partyserver: "npm:^0.0.72" + partysocket: "npm:1.1.4" + zod: "npm:^3.25.67" + peerDependencies: + react: "*" + checksum: 10c0/8b8238fade413b1ad10a2543bdac9558ef9df565503c9e6b1050942527e2b696f66c165cfe81b857add5b57fd13793689771df06a8d2cb9e7195f82d07d0457d + languageName: node + linkType: hard + "aggregate-error@npm:^3.0.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" @@ -2276,6 +2875,26 @@ __metadata: languageName: node linkType: hard +"ai@npm:^4.3.16": + version: 4.3.19 + resolution: "ai@npm:4.3.19" + dependencies: + "@ai-sdk/provider": "npm:1.1.3" + "@ai-sdk/provider-utils": "npm:2.2.8" + "@ai-sdk/react": "npm:1.2.12" + "@ai-sdk/ui-utils": "npm:1.2.11" + "@opentelemetry/api": "npm:1.9.0" + jsondiffpatch: "npm:0.6.0" + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + react: + optional: true + checksum: 10c0/738ac453b3e61b2f2282941fe8af946c42696fbdcffa5ac213823377bcddf475f26923cf2ca5656d5655e5c351e355e1af62dcb04a6df6139b67bac650b01af2 + languageName: node + linkType: hard + "ajv-draft-04@npm:^1.0.0": version: 1.0.0 resolution: "ajv-draft-04@npm:1.0.0" @@ -2646,6 +3265,13 @@ __metadata: languageName: node linkType: hard +"blake3-wasm@npm:2.1.5": + version: 2.1.5 + resolution: "blake3-wasm@npm:2.1.5" + checksum: 10c0/5dc729d8e3a9d1d7ab016b36cdda264a327ada0239716df48435163e11d2bf6df25d6e421655a1f52649098ae49555268a654729b7d02768f77c571ab37ef814 + languageName: node + linkType: hard + "body-parser@npm:^2.2.0": version: 2.2.0 resolution: "body-parser@npm:2.2.0" @@ -2999,6 +3625,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: 10c0/b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef + languageName: node + linkType: hard + "chardet@npm:^0.7.0": version: 0.7.0 resolution: "chardet@npm:0.7.0" @@ -3216,13 +3849,23 @@ __metadata: languageName: node linkType: hard -"color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 languageName: node linkType: hard +"color-string@npm:^1.9.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: "npm:^1.0.0" + simple-swizzle: "npm:^0.2.2" + checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404 + languageName: node + linkType: hard + "color-support@npm:^1.1.2, color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" @@ -3232,6 +3875,16 @@ __metadata: languageName: node linkType: hard +"color@npm:^4.2.3": + version: 4.2.3 + resolution: "color@npm:4.2.3" + dependencies: + color-convert: "npm:^2.0.1" + color-string: "npm:^1.9.0" + checksum: 10c0/7fbe7cfb811054c808349de19fb380252e5e34e61d7d168ec3353e9e9aacb1802674bddc657682e4e9730c2786592a4de6f8283e7e0d3870b829bb0b7b2f6118 + languageName: node + linkType: hard + "colors@npm:1.0.3": version: 1.0.3 resolution: "colors@npm:1.0.3" @@ -3340,6 +3993,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^1.0.2": + version: 1.0.2 + resolution: "cookie@npm:1.0.2" + checksum: 10c0/fd25fe79e8fbcfcaf6aa61cd081c55d144eeeba755206c058682257cb38c4bd6795c6620de3f064c740695bb65b7949ebb1db7a95e4636efb8357a335ad3f54b + languageName: node + linkType: hard + "core-util-is@npm:~1.0.0": version: 1.0.3 resolution: "core-util-is@npm:1.0.3" @@ -3364,6 +4024,13 @@ __metadata: languageName: node linkType: hard +"cron-schedule@npm:^5.0.4": + version: 5.0.4 + resolution: "cron-schedule@npm:5.0.4" + checksum: 10c0/8b89b4a4b90dff66e277855012d6b4b4ee18f2d3d16f74aa40e9555a0c08b2d4bc4ba8c7526cf6213490f32099f8a3a0db86942b7dbfbb1d2580cccf0ba344ae + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -3512,6 +4179,13 @@ __metadata: languageName: node linkType: hard +"defu@npm:^6.1.4": + version: 6.1.4 + resolution: "defu@npm:6.1.4" + checksum: 10c0/2d6cc366262dc0cb8096e429368e44052fdf43ed48e53ad84cc7c9407f890301aa5fcb80d0995abaaf842b3949f154d060be4160f7a46cb2bc2f7726c81526f5 + languageName: node + linkType: hard + "delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" @@ -3540,6 +4214,20 @@ __metadata: languageName: node linkType: hard +"dequal@npm:^2.0.3": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 10c0/f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.3": + version: 2.0.4 + resolution: "detect-libc@npm:2.0.4" + checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c + languageName: node + linkType: hard + "dezalgo@npm:^1.0.0": version: 1.0.4 resolution: "dezalgo@npm:1.0.4" @@ -3550,6 +4238,13 @@ __metadata: languageName: node linkType: hard +"diff-match-patch@npm:^1.0.5": + version: 1.0.5 + resolution: "diff-match-patch@npm:1.0.5" + checksum: 10c0/142b6fad627b9ef309d11bd935e82b84c814165a02500f046e2773f4ea894d10ed3017ac20454900d79d4a0322079f5b713cf0986aaf15fce0ec4a2479980c86 + languageName: node + linkType: hard + "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3" @@ -3692,6 +4387,13 @@ __metadata: languageName: node linkType: hard +"error-stack-parser-es@npm:^1.0.5": + version: 1.0.5 + resolution: "error-stack-parser-es@npm:1.0.5" + checksum: 10c0/040665eb87a42fe068c0da501bc258f3d15d3a03963c0723d7a2741e251d400c9776a52d2803afdc5709def99554cdb5a5d99c203c7eaf4885d3fbc217e2e8f7 + languageName: node + linkType: hard + "error@npm:^10.4.0": version: 10.4.0 resolution: "error@npm:10.4.0" @@ -3743,6 +4445,92 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:0.25.4": + version: 0.25.4 + resolution: "esbuild@npm:0.25.4" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.4" + "@esbuild/android-arm": "npm:0.25.4" + "@esbuild/android-arm64": "npm:0.25.4" + "@esbuild/android-x64": "npm:0.25.4" + "@esbuild/darwin-arm64": "npm:0.25.4" + "@esbuild/darwin-x64": "npm:0.25.4" + "@esbuild/freebsd-arm64": "npm:0.25.4" + "@esbuild/freebsd-x64": "npm:0.25.4" + "@esbuild/linux-arm": "npm:0.25.4" + "@esbuild/linux-arm64": "npm:0.25.4" + "@esbuild/linux-ia32": "npm:0.25.4" + "@esbuild/linux-loong64": "npm:0.25.4" + "@esbuild/linux-mips64el": "npm:0.25.4" + "@esbuild/linux-ppc64": "npm:0.25.4" + "@esbuild/linux-riscv64": "npm:0.25.4" + "@esbuild/linux-s390x": "npm:0.25.4" + "@esbuild/linux-x64": "npm:0.25.4" + "@esbuild/netbsd-arm64": "npm:0.25.4" + "@esbuild/netbsd-x64": "npm:0.25.4" + "@esbuild/openbsd-arm64": "npm:0.25.4" + "@esbuild/openbsd-x64": "npm:0.25.4" + "@esbuild/sunos-x64": "npm:0.25.4" + "@esbuild/win32-arm64": "npm:0.25.4" + "@esbuild/win32-ia32": "npm:0.25.4" + "@esbuild/win32-x64": "npm:0.25.4" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/db9f51248f0560bc46ab219461d338047617f6caf373c95f643b204760bdfa10c95b48cfde948949f7e509599ae4ab61c3f112092a3534936c6abfb800c565b0 + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.2 resolution: "escalade@npm:3.1.2" @@ -3937,6 +4725,13 @@ __metadata: languageName: node linkType: hard +"event-target-polyfill@npm:^0.0.4": + version: 0.0.4 + resolution: "event-target-polyfill@npm:0.0.4" + checksum: 10c0/7052b838df6509e8290f110daf94604dd05828834db4ad6fe4a12abc693da1a3274100cfd4ff3e47a941d5aa3fb7cdb8164d85402b93d85340bb3adc596d8732 + languageName: node + linkType: hard + "event-target-shim@npm:^5.0.0": version: 5.0.1 resolution: "event-target-shim@npm:5.0.1" @@ -3998,6 +4793,13 @@ __metadata: languageName: node linkType: hard +"exit-hook@npm:2.2.1": + version: 2.2.1 + resolution: "exit-hook@npm:2.2.1" + checksum: 10c0/0803726d1b60aade6afd10c73e5a7e1bf256ac9bee78362a88e91a4f735e8c67899f2853ddc613072c05af07bbb067a9978a740e614db1aeef167d50c6dc5c09 + languageName: node + linkType: hard + "expect@npm:^29.7.0": version: 29.7.0 resolution: "expect@npm:29.7.0" @@ -4062,6 +4864,13 @@ __metadata: languageName: node linkType: hard +"exsolve@npm:^1.0.7": + version: 1.0.7 + resolution: "exsolve@npm:1.0.7" + checksum: 10c0/4479369d0bd84bb7e0b4f5d9bc18d26a89b6dbbbccd73f9d383d14892ef78ddbe159e01781055342f83dc00ebe90044036daf17ddf55cc21e2cac6609aa15631 + languageName: node + linkType: hard + "external-editor@npm:^3.0.3": version: 3.1.0 resolution: "external-editor@npm:3.1.0" @@ -4582,6 +5391,13 @@ __metadata: languageName: node linkType: hard +"glob-to-regexp@npm:0.4.1": + version: 0.4.1 + resolution: "glob-to-regexp@npm:0.4.1" + checksum: 10c0/0486925072d7a916f052842772b61c3e86247f0a80cc0deb9b5a3e8a1a9faad5b04fb6f58986a09f34d3e96cd2a22a24b7e9882fb1cf904c31e9a310de96c429 + languageName: node + linkType: hard + "glob@npm:^10.2.2": version: 10.3.12 resolution: "glob@npm:10.3.12" @@ -4813,6 +5629,13 @@ __metadata: languageName: node linkType: hard +"hono@npm:^4.8.4": + version: 4.8.10 + resolution: "hono@npm:4.8.10" + checksum: 10c0/5a7ff01d5cbb00501678b45bba7ac9b1dce1500167dee0c5a8368037470db1f464e5f4c48a8cbd50fb3e6cd7e9f3064e6812d0f9b254c672e38b1a2b1d9ecf85 + languageName: node + linkType: hard + "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -5148,6 +5971,13 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 10c0/f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54 + languageName: node + linkType: hard + "is-binary-path@npm:~2.1.0": version: 2.1.0 resolution: "is-binary-path@npm:2.1.0" @@ -5545,6 +6375,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^6.0.11": + version: 6.0.12 + resolution: "jose@npm:6.0.12" + checksum: 10c0/e5ca51b078b2443f6ca671e14d72e0ffd21b760dac0d77cabd7af649a127376ec90665c8b25f34dd88bb31094915ee662daf76e0b33a025d28dbc2bc17413dec + languageName: node + linkType: hard + "js-sha256@npm:^0.11.0": version: 0.11.0 resolution: "js-sha256@npm:0.11.0" @@ -5640,6 +6477,13 @@ __metadata: languageName: node linkType: hard +"json-schema@npm:^0.4.0": + version: 0.4.0 + resolution: "json-schema@npm:0.4.0" + checksum: 10c0/d4a637ec1d83544857c1c163232f3da46912e971d5bf054ba44fdb88f07d8d359a462b4aec46f2745efbc57053365608d88bc1d7b1729f7b4fc3369765639ed3 + languageName: node + linkType: hard + "json-stable-stringify-without-jsonify@npm:^1.0.1": version: 1.0.1 resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" @@ -5670,6 +6514,19 @@ __metadata: languageName: node linkType: hard +"jsondiffpatch@npm:0.6.0": + version: 0.6.0 + resolution: "jsondiffpatch@npm:0.6.0" + dependencies: + "@types/diff-match-patch": "npm:^1.0.36" + chalk: "npm:^5.3.0" + diff-match-patch: "npm:^1.0.5" + bin: + jsondiffpatch: bin/jsondiffpatch.js + checksum: 10c0/f7822e48a8ef8b9f7c6024cc59b7d3707a9fe6d84fd776d169de5a1803ad551ffe7cfdc7587f3900f224bc70897355884ed43eb1c8ccd02e7f7b43a7ebcfed4f + languageName: node + linkType: hard + "jsonfile@npm:^4.0.0": version: 4.0.0 resolution: "jsonfile@npm:4.0.0" @@ -5732,6 +6589,13 @@ __metadata: languageName: node linkType: hard +"kleur@npm:^4.1.5": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 10c0/e9de6cb49657b6fa70ba2d1448fd3d691a5c4370d8f7bbf1c2f64c24d461270f2117e1b0afe8cb3114f13bbd8e51de158c2a224953960331904e636a5e4c0f2a + languageName: node + linkType: hard + "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -6122,6 +6986,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:^3.0.0": + version: 3.0.0 + resolution: "mime@npm:3.0.0" + bin: + mime: cli.js + checksum: 10c0/402e792a8df1b2cc41cb77f0dcc46472b7944b7ec29cb5bbcd398624b6b97096728f1239766d3fdeb20551dd8d94738344c195a6ea10c4f906eb0356323b0531 + languageName: node + linkType: hard + "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -6143,6 +7016,28 @@ __metadata: languageName: node linkType: hard +"miniflare@npm:4.20250730.0": + version: 4.20250730.0 + resolution: "miniflare@npm:4.20250730.0" + dependencies: + "@cspotcode/source-map-support": "npm:0.8.1" + acorn: "npm:8.14.0" + acorn-walk: "npm:8.3.2" + exit-hook: "npm:2.2.1" + glob-to-regexp: "npm:0.4.1" + sharp: "npm:^0.33.5" + stoppable: "npm:1.1.0" + undici: "npm:^7.10.0" + workerd: "npm:1.20250730.0" + ws: "npm:8.18.0" + youch: "npm:4.1.0-beta.10" + zod: "npm:3.22.3" + bin: + miniflare: bootstrap.js + checksum: 10c0/f7df02dcd5d9c731ebee21ae189eba5688d8fb89435be791caf5f20673d6ae2af55028c2624ddc53c35de8416f6f8c4d8573d0512d0273d736a11af03ed47cc1 + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -6442,6 +7337,24 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.8": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"nanoid@npm:^5.1.5": + version: 5.1.5 + resolution: "nanoid@npm:5.1.5" + bin: + nanoid: bin/nanoid.js + checksum: 10c0/e6004f1ad6c7123eeb037062c4441d44982037dc043aabb162457ef6986e99964ba98c63c975f96c547403beb0bf95bc537bd7bf9a09baf381656acdc2975c3c + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -6873,6 +7786,13 @@ __metadata: languageName: node linkType: hard +"oauth4webapi@npm:^3.5.5": + version: 3.6.0 + resolution: "oauth4webapi@npm:3.6.0" + checksum: 10c0/5bfad39628db80faa563b3da4cae342a99827399e7b8730469ece452ecab84e32951b17c09f5904125f0ba6f2f869c01c354421d29470bc90940d8c6c226eef2 + languageName: node + linkType: hard + "object-assign@npm:^4, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -6923,6 +7843,13 @@ __metadata: languageName: node linkType: hard +"ohash@npm:^2.0.11": + version: 2.0.11 + resolution: "ohash@npm:2.0.11" + checksum: 10c0/d07c8d79cc26da082c1a7c8d5b56c399dd4ed3b2bd069fcae6bae78c99a9bcc3ad813b1e1f49ca2f335292846d689c6141a762cf078727d2302a33d414e69c79 + languageName: node + linkType: hard + "on-finished@npm:^2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -7254,6 +8181,26 @@ __metadata: languageName: node linkType: hard +"partyserver@npm:^0.0.72": + version: 0.0.72 + resolution: "partyserver@npm:0.0.72" + dependencies: + nanoid: "npm:^5.1.5" + peerDependencies: + "@cloudflare/workers-types": ^4.20240729.0 + checksum: 10c0/94075ecb2f11a1517f32916fbc662b542735ade7e092042f9f9888d61ebb1d3cfd76ccaf406ff8e7c672c6812123fb075888c6b4196c7d38df10dbdc8f0d928a + languageName: node + linkType: hard + +"partysocket@npm:1.1.4": + version: 1.1.4 + resolution: "partysocket@npm:1.1.4" + dependencies: + event-target-polyfill: "npm:^0.0.4" + checksum: 10c0/d859bcaef5f5e8be0f383140a896a41e78c95fc2166002d2a60f09168c720d78864e8f8e3251ed5146d46bf9487102dd9c81d34893c811bfc8ea64f7236f2fed + languageName: node + linkType: hard + "password-prompt@npm:^1.1.2": version: 1.1.3 resolution: "password-prompt@npm:1.1.3" @@ -7331,6 +8278,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:6.3.0": + version: 6.3.0 + resolution: "path-to-regexp@npm:6.3.0" + checksum: 10c0/73b67f4638b41cde56254e6354e46ae3a2ebc08279583f6af3d96fe4664fc75788f74ed0d18ca44fa4a98491b69434f9eee73b97bb5314bd1b5adb700f5c18d6 + languageName: node + linkType: hard + "path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.1.0": version: 8.2.0 resolution: "path-to-regexp@npm:8.2.0" @@ -7345,6 +8299,13 @@ __metadata: languageName: node linkType: hard +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + "pathval@npm:^2.0.0": version: 2.0.1 resolution: "pathval@npm:2.0.1" @@ -8023,6 +8984,13 @@ __metadata: languageName: node linkType: hard +"secure-json-parse@npm:^2.7.0": + version: 2.7.0 + resolution: "secure-json-parse@npm:2.7.0" + checksum: 10c0/f57eb6a44a38a3eeaf3548228585d769d788f59007454214fab9ed7f01fbf2e0f1929111da6db28cf0bcc1a2e89db5219a59e83eeaec3a54e413a0197ce879e4 + languageName: node + linkType: hard + "semver@npm:2 || 3 || 4 || 5": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -8138,6 +9106,75 @@ __metadata: languageName: node linkType: hard +"sharp@npm:^0.33.5": + version: 0.33.5 + resolution: "sharp@npm:0.33.5" + dependencies: + "@img/sharp-darwin-arm64": "npm:0.33.5" + "@img/sharp-darwin-x64": "npm:0.33.5" + "@img/sharp-libvips-darwin-arm64": "npm:1.0.4" + "@img/sharp-libvips-darwin-x64": "npm:1.0.4" + "@img/sharp-libvips-linux-arm": "npm:1.0.5" + "@img/sharp-libvips-linux-arm64": "npm:1.0.4" + "@img/sharp-libvips-linux-s390x": "npm:1.0.4" + "@img/sharp-libvips-linux-x64": "npm:1.0.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4" + "@img/sharp-linux-arm": "npm:0.33.5" + "@img/sharp-linux-arm64": "npm:0.33.5" + "@img/sharp-linux-s390x": "npm:0.33.5" + "@img/sharp-linux-x64": "npm:0.33.5" + "@img/sharp-linuxmusl-arm64": "npm:0.33.5" + "@img/sharp-linuxmusl-x64": "npm:0.33.5" + "@img/sharp-wasm32": "npm:0.33.5" + "@img/sharp-win32-ia32": "npm:0.33.5" + "@img/sharp-win32-x64": "npm:0.33.5" + color: "npm:^4.2.3" + detect-libc: "npm:^2.0.3" + semver: "npm:^7.6.3" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10c0/6b81421ddfe6ee524d8d77e325c5e147fef22884e1c7b1656dfd89a88d7025894115da02d5f984261bf2e6daa16f98cadd1721c4ba408b4212b1d2a60f233484 + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -8263,6 +9300,15 @@ __metadata: languageName: node linkType: hard +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: "npm:^0.3.1" + checksum: 10c0/df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308 + languageName: node + linkType: hard + "sinon@npm:^19.0.2": version: 19.0.5 resolution: "sinon@npm:19.0.5" @@ -8486,6 +9532,13 @@ __metadata: languageName: node linkType: hard +"stoppable@npm:1.1.0": + version: 1.1.0 + resolution: "stoppable@npm:1.1.0" + checksum: 10c0/ba91b65e6442bf6f01ce837a727ece597a977ed92a05cb9aea6bf446c5e0dcbccc28f31b793afa8aedd8f34baaf3335398d35f903938d5493f7fbe386a1e090e + languageName: node + linkType: hard + "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -8593,6 +9646,13 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:^10.0.0": + version: 10.0.0 + resolution: "supports-color@npm:10.0.0" + checksum: 10c0/0e7884dfd02a07b3c6e0b235346f58c19f0201f1e44f7807583581761b354688c8577378785b5a4e3b03110809786c4c808e0e086cd91911f7b8bc59132703a8 + languageName: node + linkType: hard + "supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -8628,6 +9688,18 @@ __metadata: languageName: node linkType: hard +"swr@npm:^2.2.5": + version: 2.3.4 + resolution: "swr@npm:2.3.4" + dependencies: + dequal: "npm:^2.0.3" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/c5cf536c2652fc6b64d64d3ce232f8bbe25dcaffc688f852fb81cf06e28b59280ebebde752429d9801c3af8e7a956ee7242376a6386a599cedc0000b862a712d + languageName: node + linkType: hard + "tanu@npm:^0.1.13": version: 0.1.13 resolution: "tanu@npm:0.1.13" @@ -8677,6 +9749,13 @@ __metadata: languageName: node linkType: hard +"throttleit@npm:2.1.0": + version: 2.1.0 + resolution: "throttleit@npm:2.1.0" + checksum: 10c0/1696ae849522cea6ba4f4f3beac1f6655d335e51b42d99215e196a718adced0069e48deaaf77f7e89f526ab31de5b5c91016027da182438e6f9280be2f3d5265 + languageName: node + linkType: hard + "through@npm:^2.3.6": version: 2.3.8 resolution: "through@npm:2.3.8" @@ -8944,6 +10023,13 @@ __metadata: languageName: node linkType: hard +"ufo@npm:^1.6.1": + version: 1.6.1 + resolution: "ufo@npm:1.6.1" + checksum: 10c0/5a9f041e5945fba7c189d5410508cbcbefef80b253ed29aa2e1f8a2b86f4bd51af44ee18d4485e6d3468c92be9bf4a42e3a2b72dcaf27ce39ce947ec994f1e6b + languageName: node + linkType: hard + "uglify-js@npm:^3.1.4": version: 3.17.4 resolution: "uglify-js@npm:3.17.4" @@ -8960,6 +10046,26 @@ __metadata: languageName: node linkType: hard +"undici@npm:^7.10.0": + version: 7.13.0 + resolution: "undici@npm:7.13.0" + checksum: 10c0/8865d40b141f073215a6763aad5d1b2f4bd4e252600e93e68055d6c5d23a8a0e5782669236b2ecfa4d415d1d969d9c4623ff1c0386d32fa60088a19ffa58c611 + languageName: node + linkType: hard + +"unenv@npm:2.0.0-rc.19": + version: 2.0.0-rc.19 + resolution: "unenv@npm:2.0.0-rc.19" + dependencies: + defu: "npm:^6.1.4" + exsolve: "npm:^1.0.7" + ohash: "npm:^2.0.11" + pathe: "npm:^2.0.3" + ufo: "npm:^1.6.1" + checksum: 10c0/6c104af99097704e3d65562e2ec3ed876a1846df0b61b4e0714e99128d6dd6103d7022d6425bb0bfc9f6f453c52d09d152ff31f83ec118188503a9e33677bc3c + languageName: node + linkType: hard + "unique-filename@npm:^1.1.1": version: 1.1.1 resolution: "unique-filename@npm:1.1.1" @@ -9082,6 +10188,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.4.0": + version: 1.5.0 + resolution: "use-sync-external-store@npm:1.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/1b8663515c0be34fa653feb724fdcce3984037c78dd4a18f68b2c8be55cc1a1084c578d5b75f158d41b5ddffc2bf5600766d1af3c19c8e329bb20af2ec6f52f4 + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -9327,6 +10442,32 @@ __metadata: languageName: node linkType: hard +"workerd@npm:1.20250730.0": + version: 1.20250730.0 + resolution: "workerd@npm:1.20250730.0" + dependencies: + "@cloudflare/workerd-darwin-64": "npm:1.20250730.0" + "@cloudflare/workerd-darwin-arm64": "npm:1.20250730.0" + "@cloudflare/workerd-linux-64": "npm:1.20250730.0" + "@cloudflare/workerd-linux-arm64": "npm:1.20250730.0" + "@cloudflare/workerd-windows-64": "npm:1.20250730.0" + dependenciesMeta: + "@cloudflare/workerd-darwin-64": + optional: true + "@cloudflare/workerd-darwin-arm64": + optional: true + "@cloudflare/workerd-linux-64": + optional: true + "@cloudflare/workerd-linux-arm64": + optional: true + "@cloudflare/workerd-windows-64": + optional: true + bin: + workerd: bin/workerd + checksum: 10c0/cd89d802a4bdb5ca1fea3cd84883179e9ebbbeb8aadc5744aa8dceebb4f2196438da57da69d92207aae453307381a696df654ee81926834de19f1e103df1ad72 + languageName: node + linkType: hard + "workerpool@npm:^6.5.1": version: 6.5.1 resolution: "workerpool@npm:6.5.1" @@ -9334,6 +10475,34 @@ __metadata: languageName: node linkType: hard +"wrangler@npm:^4.22.0": + version: 4.27.0 + resolution: "wrangler@npm:4.27.0" + dependencies: + "@cloudflare/kv-asset-handler": "npm:0.4.0" + "@cloudflare/unenv-preset": "npm:2.5.0" + blake3-wasm: "npm:2.1.5" + esbuild: "npm:0.25.4" + fsevents: "npm:~2.3.2" + miniflare: "npm:4.20250730.0" + path-to-regexp: "npm:6.3.0" + unenv: "npm:2.0.0-rc.19" + workerd: "npm:1.20250730.0" + peerDependencies: + "@cloudflare/workers-types": ^4.20250730.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@cloudflare/workers-types": + optional: true + bin: + wrangler: bin/wrangler.js + wrangler2: bin/wrangler.js + checksum: 10c0/ef2d3218e2a9a76574cddad8a4551023fa792f69b8028b3a5eac34182a94f20318d596526f5877c3d36d21f8c2c34c3c176f44f84f2508edbbf422c6d3373cc7 + languageName: node + linkType: hard + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -9384,6 +10553,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 + languageName: node + linkType: hard + "xml2js@npm:0.6.2": version: 0.6.2 resolution: "xml2js@npm:0.6.2" @@ -9576,32 +10760,41 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:^3.24.1": - version: 3.24.6 - resolution: "zod-to-json-schema@npm:3.24.6" - peerDependencies: - zod: ^3.24.1 - checksum: 10c0/b907ab6d057100bd25a37e5545bf5f0efa5902cd84d3c3ec05c2e51541431a47bd9bf1e5e151a244273409b45f5986d55b26e5d207f98abc5200702f733eb368 +"youch-core@npm:^0.3.3": + version: 0.3.3 + resolution: "youch-core@npm:0.3.3" + dependencies: + "@poppinss/exception": "npm:^1.2.2" + error-stack-parser-es: "npm:^1.0.5" + checksum: 10c0/fe101a037a6cfaaa4e80e3d062ff33d4b087b65e3407e65220b453c9b2a66c87ea348a7da0239b61623d929d8fa0a9e139486eaa690ef5605bb49947a2fa82f6 languageName: node linkType: hard -"zod@npm:^3.19.1": - version: 3.22.4 - resolution: "zod@npm:3.22.4" - checksum: 10c0/7578ab283dac0eee66a0ad0fc4a7f28c43e6745aadb3a529f59a4b851aa10872b3890398b3160f257f4b6817b4ce643debdda4fb21a2c040adda7862cab0a587 +"youch@npm:4.1.0-beta.10": + version: 4.1.0-beta.10 + resolution: "youch@npm:4.1.0-beta.10" + dependencies: + "@poppinss/colors": "npm:^4.1.5" + "@poppinss/dumper": "npm:^0.6.4" + "@speed-highlight/core": "npm:^1.2.7" + cookie: "npm:^1.0.2" + youch-core: "npm:^0.3.3" + checksum: 10c0/588d65aa5837a46c8473cf57a9129115383f57aad5899915d37005950decfefc66bec85b8a1262dbefd623a122c279095074655889317311a554f9c2e290a5b3 languageName: node linkType: hard -"zod@npm:^3.23.8": - version: 3.25.75 - resolution: "zod@npm:3.25.75" - checksum: 10c0/e11c83dcd1437401c1edf4f0448bd13b9133e83196385d0ee5407662bef6c08099cb511b5e65f11f2feba244b6709bcd34077c9450c9f3064f75df51b362b5f2 +"zod-to-json-schema@npm:^3.24.1": + version: 3.24.6 + resolution: "zod-to-json-schema@npm:3.24.6" + peerDependencies: + zod: ^3.24.1 + checksum: 10c0/b907ab6d057100bd25a37e5545bf5f0efa5902cd84d3c3ec05c2e51541431a47bd9bf1e5e151a244273409b45f5986d55b26e5d207f98abc5200702f733eb368 languageName: node linkType: hard -"zod@npm:^3.24.2": - version: 3.24.2 - resolution: "zod@npm:3.24.2" - checksum: 10c0/c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565 +"zod@npm:3.24.1": + version: 3.24.1 + resolution: "zod@npm:3.24.1" + checksum: 10c0/0223d21dbaa15d8928fe0da3b54696391d8e3e1e2d0283a1a070b5980a1dbba945ce631c2d1eccc088fdbad0f2dfa40155590bf83732d3ac4fcca2cc9237591b languageName: node linkType: hard