#
tokens: 28509/50000 8/92 files (page 3/3)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 3. Use http://codebase.md/severity1/terraform-cloud-mcp?page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── .python-version
├── CLAUDE.md
├── docs
│   ├── API_REFERENCES.md
│   ├── CLAUDE.md
│   ├── CONTRIBUTING.md
│   ├── conversations
│   │   ├── account.md
│   │   ├── apply-management-conversation.md
│   │   ├── assessment-results-conversation.md
│   │   ├── cost-estimate-conversation.md
│   │   ├── organization-entitlements-conversation.md
│   │   ├── organizations-management-conversation.md
│   │   ├── plan-management-conversation.md
│   │   ├── project-management-conversation.md
│   │   ├── runs-management-conversation.md
│   │   ├── state_management.md
│   │   ├── variables-conversation.md
│   │   └── workspace-management-conversation.md
│   ├── DEVELOPMENT.md
│   ├── FILTERING_SYSTEM.md
│   ├── models
│   │   ├── account.md
│   │   ├── apply.md
│   │   ├── assessment_result.md
│   │   ├── cost_estimate.md
│   │   ├── organization.md
│   │   ├── plan.md
│   │   ├── project.md
│   │   ├── run.md
│   │   ├── state_version_outputs.md
│   │   ├── state_versions.md
│   │   ├── variables.md
│   │   └── workspace.md
│   ├── README.md
│   └── tools
│       ├── account.md
│       ├── apply.md
│       ├── assessment_results.md
│       ├── cost_estimate.md
│       ├── organization.md
│       ├── plan.md
│       ├── project.md
│       ├── run.md
│       ├── state_version_outputs.md
│       ├── state_versions.md
│       ├── variables.md
│       └── workspace.md
├── env.example
├── LICENSE
├── mypy.ini
├── pyproject.toml
├── README.md
├── terraform_cloud_mcp
│   ├── __init__.py
│   ├── api
│   │   ├── __init__.py
│   │   ├── CLAUDE.md
│   │   └── client.py
│   ├── configs
│   │   ├── __init__.py
│   │   ├── CLAUDE.md
│   │   └── filter_configs.py
│   ├── models
│   │   ├── __init__.py
│   │   ├── account.py
│   │   ├── applies.py
│   │   ├── assessment_results.py
│   │   ├── base.py
│   │   ├── CLAUDE.md
│   │   ├── cost_estimates.py
│   │   ├── filters.py
│   │   ├── organizations.py
│   │   ├── plans.py
│   │   ├── projects.py
│   │   ├── runs.py
│   │   ├── state_version_outputs.py
│   │   ├── state_versions.py
│   │   ├── variables.py
│   │   └── workspaces.py
│   ├── server.py
│   ├── tools
│   │   ├── __init__.py
│   │   ├── account.py
│   │   ├── applies.py
│   │   ├── assessment_results.py
│   │   ├── CLAUDE.md
│   │   ├── cost_estimates.py
│   │   ├── organizations.py
│   │   ├── plans.py
│   │   ├── projects.py
│   │   ├── runs.py
│   │   ├── state_version_outputs.py
│   │   ├── state_versions.py
│   │   ├── variables.py
│   │   └── workspaces.py
│   └── utils
│       ├── __init__.py
│       ├── CLAUDE.md
│       ├── decorators.py
│       ├── env.py
│       ├── filters.py
│       ├── payload.py
│       └── request.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/terraform_cloud_mcp/models/variables.py:
--------------------------------------------------------------------------------

```python
"""Variable models for Terraform Cloud API

This module contains models for Terraform Cloud variable-related requests,
including workspace variables and variable sets.

Reference:
- https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-variables
- https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets
"""

from enum import Enum
from typing import List, Optional

from pydantic import Field

from .base import APIRequest


class VariableCategory(str, Enum):
    """Variable category options for workspace variables.

    Defines the type of variable:
    - TERRAFORM: Terraform input variables available in configuration
    - ENV: Environment variables available during plan/apply operations

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-variables

    See:
        docs/models/variables.md for reference
    """

    TERRAFORM = "terraform"
    ENV = "env"


class WorkspaceVariable(APIRequest):
    """Model for workspace variable data.

    Represents a variable that can be set on a workspace, including
    Terraform input variables and environment variables.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-variables

    See:
        docs/models/variables.md for reference
    """

    key: str = Field(
        ...,
        description="Variable name/key",
        min_length=1,
        max_length=255,
    )
    value: Optional[str] = Field(
        None,
        description="Variable value",
        max_length=256000,
    )
    description: Optional[str] = Field(
        None,
        description="Description of the variable",
        max_length=512,
    )
    category: VariableCategory = Field(
        ...,
        description="Variable category (terraform or env)",
    )
    hcl: Optional[bool] = Field(
        False,
        description="Whether the value is HCL code (only valid for terraform variables)",
    )
    sensitive: Optional[bool] = Field(
        False,
        description="Whether the variable value is sensitive",
    )


class WorkspaceVariableParams(APIRequest):
    """Parameters for workspace variable operations without routing fields.

    This model provides all optional parameters for creating or updating workspace
    variables, separating configuration parameters from routing information like
    workspace ID and variable ID.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-variables

    See:
        docs/models/variables.md for reference
    """

    key: Optional[str] = Field(
        None,
        description="Variable name/key",
        min_length=1,
        max_length=255,
    )
    value: Optional[str] = Field(
        None,
        description="Variable value",
        max_length=256000,
    )
    description: Optional[str] = Field(
        None,
        description="Description of the variable",
        max_length=512,
    )
    category: Optional[VariableCategory] = Field(
        None,
        description="Variable category (terraform or env)",
    )
    hcl: Optional[bool] = Field(
        None,
        description="Whether the value is HCL code (only valid for terraform variables)",
    )
    sensitive: Optional[bool] = Field(
        None,
        description="Whether the variable value is sensitive",
    )


class WorkspaceVariableCreateRequest(APIRequest):
    """Request model for creating workspace variables.

    Used for POST /workspaces/:workspace_id/vars endpoint.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-variables

    See:
        docs/models/variables.md for reference
    """

    workspace_id: str = Field(
        ...,
        description="The workspace ID",
        pattern=r"^ws-[a-zA-Z0-9]{16}$",
    )
    key: str = Field(
        ...,
        description="Variable name/key",
        min_length=1,
        max_length=255,
    )
    category: VariableCategory = Field(
        ...,
        description="Variable category (terraform or env)",
    )
    value: Optional[str] = Field(
        None,
        description="Variable value",
        max_length=256000,
    )
    description: Optional[str] = Field(
        None,
        description="Description of the variable",
        max_length=512,
    )
    hcl: Optional[bool] = Field(
        None,
        description="Whether the value is HCL code (only valid for terraform variables)",
    )
    sensitive: Optional[bool] = Field(
        None,
        description="Whether the variable value is sensitive",
    )


class WorkspaceVariableUpdateRequest(APIRequest):
    """Request model for updating workspace variables.

    Used for PATCH /workspaces/:workspace_id/vars/:variable_id endpoint.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-variables

    See:
        docs/models/variables.md for reference
    """

    workspace_id: str = Field(
        ...,
        description="The workspace ID",
        pattern=r"^ws-[a-zA-Z0-9]{16}$",
    )
    variable_id: str = Field(
        ...,
        description="The variable ID",
        pattern=r"^var-[a-zA-Z0-9]{16}$",
    )
    key: Optional[str] = Field(
        None,
        description="Variable name/key",
        min_length=1,
        max_length=255,
    )
    value: Optional[str] = Field(
        None,
        description="Variable value",
        max_length=256000,
    )
    description: Optional[str] = Field(
        None,
        description="Description of the variable",
        max_length=512,
    )
    category: Optional[VariableCategory] = Field(
        None,
        description="Variable category (terraform or env)",
    )
    hcl: Optional[bool] = Field(
        None,
        description="Whether the value is HCL code (only valid for terraform variables)",
    )
    sensitive: Optional[bool] = Field(
        None,
        description="Whether the variable value is sensitive",
    )


# Variable Sets Models


class VariableSet(APIRequest):
    """Model for variable set data.

    Represents a collection of variables that can be applied to multiple
    workspaces or projects.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets

    See:
        docs/models/variables.md for reference
    """

    name: str = Field(
        ...,
        description="Variable set name",
        min_length=1,
        max_length=90,
    )
    description: Optional[str] = Field(
        None,
        description="Description of the variable set",
        max_length=512,
    )
    global_: Optional[bool] = Field(
        False,
        alias="global",
        description="Whether this is a global variable set",
    )
    priority: Optional[bool] = Field(
        False,
        description="Whether this variable set takes priority over workspace variables",
    )


class VariableSetParams(APIRequest):
    """Parameters for variable set operations without routing fields.

    This model provides all optional parameters for creating or updating variable
    sets, separating configuration parameters from routing information.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets

    See:
        docs/models/variables.md for reference
    """

    name: Optional[str] = Field(
        None,
        description="Variable set name",
        min_length=1,
        max_length=90,
    )
    description: Optional[str] = Field(
        None,
        description="Description of the variable set",
        max_length=512,
    )
    global_: Optional[bool] = Field(
        None,
        alias="global",
        description="Whether this is a global variable set",
    )
    priority: Optional[bool] = Field(
        None,
        description="Whether this variable set takes priority over workspace variables",
    )


class VariableSetCreateRequest(APIRequest):
    """Request model for creating variable sets.

    Used for POST /organizations/:organization/varsets endpoint.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets

    See:
        docs/models/variables.md for reference
    """

    organization: str = Field(
        ...,
        description="The organization name",
        min_length=1,
    )
    name: str = Field(
        ...,
        description="Variable set name",
        min_length=1,
        max_length=90,
    )
    params: Optional[VariableSetParams] = Field(
        None,
        description="Additional variable set parameters",
    )


class VariableSetUpdateRequest(APIRequest):
    """Request model for updating variable sets.

    Used for PATCH /varsets/:varset_id endpoint.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets

    See:
        docs/models/variables.md for reference
    """

    varset_id: str = Field(
        ...,
        description="The variable set ID",
        pattern=r"^varset-[a-zA-Z0-9]{16}$",
    )
    params: Optional[VariableSetParams] = Field(
        None,
        description="Variable set parameters to update",
    )


class VariableSetVariable(APIRequest):
    """Model for variables within a variable set.

    Represents a variable that belongs to a variable set with the same
    structure as workspace variables.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets

    See:
        docs/models/variables.md for reference
    """

    key: str = Field(
        ...,
        description="Variable name/key",
        min_length=1,
        max_length=255,
    )
    value: Optional[str] = Field(
        None,
        description="Variable value",
        max_length=256000,
    )
    description: Optional[str] = Field(
        None,
        description="Description of the variable",
        max_length=512,
    )
    category: VariableCategory = Field(
        ...,
        description="Variable category (terraform or env)",
    )
    hcl: Optional[bool] = Field(
        False,
        description="Whether the value is HCL code (only valid for terraform variables)",
    )
    sensitive: Optional[bool] = Field(
        False,
        description="Whether the variable value is sensitive",
    )


class VariableSetAssignmentRequest(APIRequest):
    """Request model for assigning variable sets to workspaces or projects.

    Used for POST /varsets/:varset_id/relationships/workspaces or projects endpoints.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets

    See:
        docs/models/variables.md for reference
    """

    varset_id: str = Field(
        ...,
        description="The variable set ID",
        pattern=r"^varset-[a-zA-Z0-9]{16}$",
    )
    workspace_ids: Optional[List[str]] = Field(
        None,
        description="List of workspace IDs to assign the variable set to",
    )
    project_ids: Optional[List[str]] = Field(
        None,
        description="List of project IDs to assign the variable set to",
    )


# Variable Set Variables Models


class VariableSetVariableParams(APIRequest):
    """Parameters for variable set variable operations without routing fields.

    This model provides all optional parameters for creating or updating variables
    within variable sets, separating configuration parameters from routing information
    like variable set ID and variable ID.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets

    See:
        docs/models/variables.md for reference
    """

    key: Optional[str] = Field(
        None,
        description="Variable name/key",
        min_length=1,
        max_length=255,
    )
    value: Optional[str] = Field(
        None,
        description="Variable value",
        max_length=256000,
    )
    description: Optional[str] = Field(
        None,
        description="Description of the variable",
        max_length=512,
    )
    category: Optional[VariableCategory] = Field(
        None,
        description="Variable category (terraform or env)",
    )
    hcl: Optional[bool] = Field(
        None,
        description="Whether the value is HCL code (only valid for terraform variables)",
    )
    sensitive: Optional[bool] = Field(
        None,
        description="Whether the variable value is sensitive",
    )


# List Request Models


class VariableSetListRequest(APIRequest):
    """Request model for listing variable sets.

    Used for GET /organizations/:organization/varsets endpoint.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets

    See:
        docs/models/variables.md for reference
    """

    organization: str = Field(
        ...,
        description="The organization name",
        min_length=1,
    )
    page_number: int = Field(
        1,
        description="The page number to return",
        ge=1,
    )
    page_size: int = Field(
        20,
        description="The number of items per page",
        ge=1,
        le=100,
    )

```

--------------------------------------------------------------------------------
/docs/conversations/runs-management-conversation.md:
--------------------------------------------------------------------------------

```markdown
# Run Management - Example Conversations

This document shows example conversations with Claude using the Terraform Cloud MCP run management features.

## Creating and Managing Runs

```
**User**: Create a plan for my "production" workspace

**Claude**: I'll create a plan-only run in your "production" workspace.

[Claude uses get_workspace_details tool to find the production workspace]
[Claude uses create_run tool with plan_only=True]

I've created a plan-only run in the "production" workspace. Here are the details:

**Run Details:**
- Run ID: run-J7cW8p2qRmYx3Dkv
- Status: Pending
- Created by: Your account ([email protected])
- Message: "Plan requested via Claude"
- Type: Plan only (will not auto-apply)

The run has been queued and will start shortly. Since this is a plan-only run, you'll need to review the plan output before applying any changes. I'll let you know when the plan is complete.
```

```
**User**: Create a destroy plan for "dev-test" workspace

**Claude**: I'll create a destroy plan for your "dev-test" workspace.

[Claude uses get_workspace_details tool to find the dev-test workspace]
[Claude uses create_run tool with is_destroy=True and plan_only=True]

I've created a destroy plan in the "dev-test" workspace. Here are the details:

**Run Details:**
- Run ID: run-L2tP6k8xZnQv1Bmj
- Status: Pending
- Created by: Your account ([email protected])
- Message: "Destroy plan requested via Claude"
- Type: Destroy plan (will delete all resources)
- Auto-apply: No (plan only)

This run will create a plan to destroy all resources managed by the "dev-test" workspace. Since this is a plan-only run, you'll need to review and explicitly approve the destruction before any resources are deleted.
```

```
**User**: Run a targeted plan in "staging" that only affects the database resources

**Claude**: I'll create a targeted plan in the "staging" workspace that only affects database resources.

[Claude uses get_workspace_details tool to find the staging workspace]
[Claude uses create_run tool with target_addrs for database resources]

I've created a targeted plan in the "staging" workspace that will only affect database resources. Here are the details:

**Run Details:**
- Run ID: run-K3jR7m9zXpYw2Ctn
- Status: Pending
- Created by: Your account ([email protected])
- Message: "Targeted plan for database resources only"
- Type: Plan
- Targets: 
  - module.database
  - aws_rds_cluster.main
  - aws_rds_instance.replicas

The run has been queued and will start shortly. This plan will only show changes related to the database resources you specified. Other resources in the workspace will not be affected by this run.
```

```
**User**: Create a plan with custom variables in the "development" workspace

**Claude**: I'll create a run with custom variables in the "development" workspace.

[Claude uses get_workspace_details tool to find the development workspace]
[Claude uses create_run tool with custom variables for environment, instance_type, and enable_monitoring]

I've created a plan in the "development" workspace with the custom variables you specified. Here are the details:

**Run Details:**
- Run ID: run-P8vT5q2nWxYz1Fmb
- Status: Pending
- Created by: Your account ([email protected])
- Message: "Plan with custom variable values"

**Custom Variables for this Run:**
- environment = "dev"
- instance_type = "t3.large"
- enable_monitoring = "true"

These custom variable values will override the workspace's default values for this specific run only. The run has been queued and will start shortly.
```

```
**User**: Create a run in "sandbox" workspace with auto-apply enabled

**Claude**: I'll create a run with auto-apply enabled in the "sandbox" workspace.

[Claude uses get_workspace_details tool to find the sandbox workspace]
[Claude uses create_run tool with auto_apply=True]

I've created a run with auto-apply enabled in the "sandbox" workspace. Here are the details:

**Run Details:**
- Run ID: run-Q9wU6r3oZyXa2Gnd
- Status: Pending
- Created by: Your account ([email protected])
- Message: "Run with auto-apply enabled"
- Auto-apply: Yes (will automatically apply after successful plan)

The run has been queued and will start shortly. Since auto-apply is enabled for this run, it will automatically apply the changes after the plan completes successfully, without requiring manual approval.
```

## Listing and Viewing Runs

```
**User**: List all runs in my "production" workspace

**Claude**: I'll list all runs in your "production" workspace.

[Claude uses get_workspace_details tool to find the production workspace]
[Claude uses list_runs_in_workspace tool to get all runs]

Here are the recent runs in your "production" workspace:

1. **run-J7cW8p2qRmYx3Dkv** - "Plan requested via Claude"
   - Created: 10 minutes ago
   - Status: Planned (awaiting approval)
   - Created by: You ([email protected])
   - Type: Plan only

2. **run-K4jR7p9qZnYw2Lmp** - "Weekly infrastructure update"
   - Created: Yesterday at 14:32
   - Status: Applied
   - Created by: CI/CD Pipeline
   - Changes: 3 to add, 1 to change, 0 to destroy

3. **run-L5kS8q1rAoZx3Mnq** - "Security patch application"
   - Created: 3 days ago
   - Status: Applied
   - Created by: [email protected]
   - Changes: 0 to add, 5 to change, 0 to destroy

4. **run-M6lT9r2sBpAy4Nod** - "Roll back configuration change"
   - Created: 5 days ago
   - Status: Discarded
   - Created by: [email protected]
   - Changes: 0 to add, 3 to change, 0 to destroy

5. **run-N7mU0s3tCqBz5Ope** - "Add new monitoring resources"
   - Created: 7 days ago
   - Status: Applied
   - Created by: You ([email protected])
   - Changes: 8 to add, 0 to change, 0 to destroy
```

```
**User**: Show me runs in "example-org" that have errored in the last month

**Claude**: I'll find runs in the "example-org" organization that have errored in the last month.

[Claude uses list_runs_in_organization tool with filter_status="errored"]

I found the following runs in "example-org" that have errored in the last month:

1. **run-P8vT5q2nWxYz1Fmb** - "Plan with custom variable values"
   - Workspace: development
   - Created: Today at 09:45
   - Status: Errored (Planning failed)
   - Error: "Invalid value for variable 'instance_type'"

2. **run-S9pV6u3rDsEa8Ijf** - "Deploy new database resources"
   - Workspace: prod-db-cluster
   - Created: Yesterday at 18:22
   - Status: Errored (Apply failed)
   - Error: "Error creating RDS instance: InsufficientDBInstanceCapacity"

3. **run-T0qW7v4sEtFb9Jkg** - "Update network configuration"
   - Workspace: network-infra
   - Created: 3 days ago
   - Status: Errored (Planning failed)
   - Error: "Provider configuration not found for aws"

4. **run-U1rX8w5tFuGc0Klh** - "Scale up application servers"
   - Workspace: app-servers
   - Created: 5 days ago
   - Status: Errored (Apply failed)
   - Error: "Error applying Auto Scaling configuration: ValidationError"

5. **run-V2sY9x6uGvHd1Lmi** - "Security patch deployment"
   - Workspace: security-baseline
   - Created: 2 weeks ago
   - Status: Errored (Planning timed out)
   - Error: "Terraform operation timed out after 1 hour"
```

```
**User**: Find all destroy plan runs in my organization

**Claude**: I'll find all destroy plan runs in your organization.

[Claude uses list_runs_in_organization tool to search for destroy runs across all workspaces]

Here are the destroy plan runs I found in the "example-org" organization:

1. **run-L2tP6k8xZnQv1Bmj** - "Destroy plan requested via Claude"
   - Workspace: dev-test
   - Created: Today at 09:32
   - Status: Planned (awaiting approval)
   - Type: Destroy (will delete all resources)
   - Created by: You ([email protected])

2. **run-W3tZ0y7vHwIe2Mnj** - "Decommission test environment"
   - Workspace: feature-test
   - Created: 3 days ago
   - Status: Applied
   - Type: Destroy (deleted all resources)
   - Created by: [email protected]
   - Result: 0 to add, 0 to change, 27 to destroy

3. **run-X4uA1z8wIxJf3Nok** - "Clean up temporary resources"
   - Workspace: sandbox
   - Created: 1 week ago
   - Status: Applied
   - Type: Destroy (partial)
   - Created by: [email protected]
   - Result: 0 to add, 0 to change, 5 to destroy

4. **run-Y5vB2a9xJyKg4Opl** - "Remove deprecated services"
   - Workspace: legacy-systems
   - Created: 2 weeks ago
   - Status: Discarded
   - Type: Destroy (partial)
   - Created by: CI/CD Pipeline
```

```
**User**: Get details for run ID "run-J7cW8p2qRmYx3Dkv"

**Claude**: I'll get the details for run ID "run-J7cW8p2qRmYx3Dkv".

[Claude uses get_run_details tool with the specified run ID]

Here are the details for run ID "run-J7cW8p2qRmYx3Dkv":

**Basic Information:**
- Run ID: run-J7cW8p2qRmYx3Dkv
- Status: Planned and finished (awaiting approval)
- Message: "Plan requested via Claude"
- Created: Today at 09:30 (10 minutes ago)
- Created by: You ([email protected])

**Workspace:**
- Name: production
- Organization: example-org
- Terraform Version: 1.5.2

**Plan Details:**
- Resources to add: 2
- Resources to change: 1
- Resources to destroy: 0
- Changes summary:
  - Adding 2 AWS Lambda functions
  - Updating IAM role permissions

**Phases:**
- Plan started: Today at 09:30:15
- Plan completed: Today at 09:31:45
- Plan duration: 90 seconds
- Apply phase: Awaiting approval

**Configuration:**
- VCS commit: 7a8b9c0d... (feat: add new lambda functions)
- Plan only: Yes (requires manual apply)
- Auto-apply: No
- Terraform Version: 1.5.2
- Working directory: /terraform

The run is currently awaiting your approval to apply the plan. You can apply it using the apply_run function with this run ID.
```

## Run Actions

```
**User**: Apply the pending run for "staging" workspace

**Claude**: I'll apply the pending run for the "staging" workspace.

[Claude uses list_runs_in_workspace tool to find pending runs in staging workspace]
[Claude uses apply_run tool with a review comment]

I've applied the pending run (run-K3jR7m9zXpYw2Ctn) in the "staging" workspace. Here's the status:

**Apply Initiated:**
- Run ID: run-K3jR7m9zXpYw2Ctn
- Status: Applying
- Comment: "Applied via Claude after review"
- Action taken: Apply started

The run is now applying the planned changes to your infrastructure. The apply process typically takes a few minutes to complete. The targeted changes to your database resources are now being executed.
```

```
**User**: Cancel the run "run-P8vT5q2nWxYz1Fmb", it's taking too long

**Claude**: I'll cancel the run "run-P8vT5q2nWxYz1Fmb" that's taking too long.

[Claude uses cancel_run tool with the specified run ID and cancellation reason]

I've initiated the cancellation of run "run-P8vT5q2nWxYz1Fmb". Here's the status:

**Cancellation Initiated:**
- Run ID: run-P8vT5q2nWxYz1Fmb
- Status: Cancelling
- Comment: "Cancelled due to long execution time"

The system is now gracefully stopping the run. It may take a moment to completely cancel as Terraform needs to clean up any in-progress operations. Once cancelled, the workspace will be unlocked and available for new runs.
```

```
**User**: Discard the plan for the "dev" workspace, those changes aren't needed

**Claude**: I'll discard the pending plan for the "dev" workspace.

[Claude uses list_runs_in_workspace tool to find pending runs in dev workspace]
[Claude uses discard_run tool with reason for discarding]

I've discarded the pending plan (run-Z6wC3b0yKzLh5Pqm) in the "dev" workspace. Here's the confirmation:

**Plan Discarded:**
- Run ID: run-Z6wC3b0yKzLh5Pqm
- Status: Discarded
- Comment: "Changes no longer needed"
- Created by: You ([email protected])

The plan has been discarded and will not be applied. The workspace is now unlocked and available for new runs. No changes were made to your infrastructure.
```

```
**User**: Force cancel the stuck run in "production"

**Claude**: I'll force cancel the stuck run in the "production" workspace.

[Claude uses list_runs_in_workspace tool to find running runs in production workspace]
[Claude uses cancel_run tool first, then force_cancel_run tool for the stuck run]

I've initiated a force cancel for the stuck run (run-A7xD4c1zLaMe6Rqn) in the "production" workspace. Here's the status:

**Force Cancellation Initiated:**
- Run ID: run-A7xD4c1zLaMe6Rqn
- Status: Force cancelling
- Comment: "Force cancelling stuck run"

I first attempted a normal cancel operation, and then proceeded with the force cancel to immediately terminate the run. This will immediately unlock the workspace, allowing you to queue new runs. Note that force cancelling may leave some Terraform state operations incomplete, but it's necessary when a run is truly stuck.
```

```
**User**: I need to execute this run immediately, force execute it

**Claude**: I'll force execute a run to bypass the queue and start it immediately.

[Claude uses list_runs_in_organization tool to find pending runs in the queue]
[Claude uses force_execute_run tool to prioritize the run]

I've force executed the pending run (run-B8yE5d2aLbNf7Sro). Here's what happened:

**Force Execution Initiated:**
- Run ID: run-B8yE5d2aLbNf7Sro
- Status: Running (Planning)
- Workspace: deployment
- Original position: #3 in queue

The force execute command:
1. Cancelled all prior runs that were blocking this run
2. Unlocked the workspace
3. Started this run immediately

The run is now actively planning and has bypassed the queue. This is useful in urgent situations, but should be used sparingly as it cancels other queued runs.
```
```

--------------------------------------------------------------------------------
/docs/tools/variables.md:
--------------------------------------------------------------------------------

```markdown
# Variable Tools

This module provides tools for managing variables and variable sets in Terraform Cloud.

## Overview

Variables in Terraform Cloud are used to provide inputs to your Terraform configurations and set environment variables during runs. These tools allow you to manage variables at the workspace level and create reusable variable sets that can be shared across multiple workspaces and projects.

## API Reference

These tools interact with the Terraform Cloud Variables API:
- [Workspace Variables API Documentation](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-variables)
- [Variable Sets API Documentation](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets)
- [Variables in Terraform Cloud](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/variables)

## Tools Reference

### list_workspace_variables

**Function:** `list_workspace_variables(workspace_id: str) -> Dict[str, Any]`

**Description:** Retrieves all variables (both Terraform and environment) configured for a specific workspace.

**Parameters:**
- `workspace_id` (str): The ID of the workspace (format: "ws-xxxxxxxx")

**Returns:** JSON response containing list of workspace variables with their configuration and values.

**Notes:**
- Requires "read variables" permission on the workspace
- Sensitive variable values are not returned in the response
- Both Terraform and environment variables are included

### create_workspace_variable

**Function:** `create_workspace_variable(workspace_id: str, key: str, category: str, params: Optional[WorkspaceVariableParams] = None) -> Dict[str, Any]`

**Description:** Creates a new Terraform or environment variable within a workspace.

**Parameters:**
- `workspace_id` (str): The ID of the workspace (format: "ws-xxxxxxxx")
- `key` (str): The variable name/key
- `category` (str): Variable category ("terraform" or "env")
- `params` (WorkspaceVariableParams, optional): Additional variable parameters including value, description, HCL format, and sensitivity settings

**Returns:** JSON response with the created variable including its configuration and metadata.

**Notes:**
- Requires "write variables" permission on the workspace
- HCL format is only valid for terraform category variables
- Sensitive variables will have their values hidden in responses

### update_workspace_variable

**Function:** `update_workspace_variable(workspace_id: str, variable_id: str, params: Optional[WorkspaceVariableParams] = None) -> Dict[str, Any]`

**Description:** Modifies the configuration of an existing workspace variable. Only specified attributes will be updated.

**Parameters:**
- `workspace_id` (str): The ID of the workspace (format: "ws-xxxxxxxx")
- `variable_id` (str): The ID of the variable (format: "var-xxxxxxxx")
- `params` (WorkspaceVariableParams, optional): Variable parameters to update including key, value, description, category, HCL format, and sensitivity

**Returns:** JSON response with the updated variable including all current settings.

**Notes:**
- Requires "write variables" permission on the workspace
- Only specified attributes are updated; others remain unchanged
- Cannot change a sensitive variable to non-sensitive

### delete_workspace_variable

**Function:** `delete_workspace_variable(workspace_id: str, variable_id: str) -> Dict[str, Any]`

**Description:** Permanently removes a variable from a workspace. This action cannot be undone.

**Parameters:**
- `workspace_id` (str): The ID of the workspace (format: "ws-xxxxxxxx")
- `variable_id` (str): The ID of the variable (format: "var-xxxxxxxx")

**Returns:** Empty response with HTTP 204 status code if successful.

**Notes:**
- DESTRUCTIVE OPERATION: Requires `ENABLE_DELETE_TOOLS=true` environment variable
- Requires "write variables" permission on the workspace
- This action cannot be undone

### list_variable_sets

**Function:** `list_variable_sets(organization: str, page_number: int = 1, page_size: int = 20) -> Dict[str, Any]`

**Description:** Retrieves a paginated list of all variable sets in a Terraform Cloud organization.

**Parameters:**
- `organization` (str): The name of the organization
- `page_number` (int, optional): The page number to return (default: 1)
- `page_size` (int, optional): Number of items per page (default: 20, max: 100)

**Returns:** JSON response containing paginated list of variable sets with their configuration and metadata.

**Notes:**
- Requires "read variable sets" permission on the organization
- Variable sets allow you to reuse variables across multiple workspaces
- Results are paginated with metadata indicating total count

### get_variable_set

**Function:** `get_variable_set(varset_id: str) -> Dict[str, Any]`

**Description:** Retrieves comprehensive information about a variable set including its variables, workspace assignments, and configuration.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")

**Returns:** JSON response with variable set details including configuration and relationships.

**Notes:**
- Requires "read variable sets" permission
- Includes information about workspace and project assignments
- Shows global status and priority settings

### create_variable_set

**Function:** `create_variable_set(organization: str, name: str, params: Optional[VariableSetParams] = None) -> Dict[str, Any]`

**Description:** Creates a new variable set which can be used to manage variables across multiple workspaces and projects.

**Parameters:**
- `organization` (str): The name of the organization
- `name` (str): The name to give the variable set
- `params` (VariableSetParams, optional): Additional variable set parameters including description, global status, and priority settings

**Returns:** JSON response with the created variable set including its configuration and metadata.

**Notes:**
- Requires "write variable sets" permission on the organization
- Global variable sets are automatically applied to all workspaces
- Priority variable sets override workspace-level variables

### update_variable_set

**Function:** `update_variable_set(varset_id: str, params: Optional[VariableSetParams] = None) -> Dict[str, Any]`

**Description:** Modifies the settings of a variable set. Only specified attributes will be updated.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")
- `params` (VariableSetParams, optional): Variable set parameters to update including name, description, global status, and priority

**Returns:** JSON response with the updated variable set including all current settings.

**Notes:**
- Requires "write variable sets" permission
- Only specified attributes are updated; others remain unchanged
- Changing global status affects workspace assignments

### delete_variable_set

**Function:** `delete_variable_set(varset_id: str) -> Dict[str, Any]`

**Description:** Permanently removes a variable set and all its variables. The variable set will be unassigned from all workspaces and projects.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")

**Returns:** Empty response with HTTP 204 status code if successful.

**Notes:**
- DESTRUCTIVE OPERATION: Requires `ENABLE_DELETE_TOOLS=true` environment variable
- Requires "write variable sets" permission
- This action cannot be undone and removes all variables in the set

### assign_variable_set_to_workspaces

**Function:** `assign_variable_set_to_workspaces(varset_id: str, workspace_ids: List[str]) -> Dict[str, Any]`

**Description:** Makes the variables in a variable set available to the specified workspaces.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")
- `workspace_ids` (List[str]): List of workspace IDs (format: ["ws-xxxxxxxx", ...])

**Returns:** Empty response with HTTP 204 status code if successful.

**Notes:**
- Variables from variable sets take precedence over workspace variables if the variable set has priority enabled
- Does not affect global variable sets (they're automatically applied)

### unassign_variable_set_from_workspaces

**Function:** `unassign_variable_set_from_workspaces(varset_id: str, workspace_ids: List[str]) -> Dict[str, Any]`

**Description:** Removes the variable set assignment from the specified workspaces. The variables will no longer be available in those workspaces.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")
- `workspace_ids` (List[str]): List of workspace IDs (format: ["ws-xxxxxxxx", ...])

**Returns:** Empty response with HTTP 204 status code if successful.

**Notes:**
- Cannot unassign global variable sets from workspaces
- Variables become unavailable immediately in the affected workspaces

### assign_variable_set_to_projects

**Function:** `assign_variable_set_to_projects(varset_id: str, project_ids: List[str]) -> Dict[str, Any]`

**Description:** Makes the variables in a variable set available to all workspaces within the specified projects.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")
- `project_ids` (List[str]): List of project IDs (format: ["prj-xxxxxxxx", ...])

**Returns:** Empty response with HTTP 204 status code if successful.

**Notes:**
- All workspaces in the project receive the variable set
- New workspaces added to the project will automatically inherit the variable set

### unassign_variable_set_from_projects

**Function:** `unassign_variable_set_from_projects(varset_id: str, project_ids: List[str]) -> Dict[str, Any]`

**Description:** Removes the variable set assignment from the specified projects. The variables will no longer be available in workspaces within those projects.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")
- `project_ids` (List[str]): List of project IDs (format: ["prj-xxxxxxxx", ...])

**Returns:** Empty response with HTTP 204 status code if successful.

**Notes:**
- Affects all workspaces within the specified projects
- Cannot unassign global variable sets from projects

### list_variables_in_variable_set

**Function:** `list_variables_in_variable_set(varset_id: str) -> Dict[str, Any]`

**Description:** Retrieves all variables that belong to a specific variable set, including their configuration and values.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")

**Returns:** JSON response containing list of variables in the variable set with their configuration.

**Notes:**
- Requires "read variable sets" permission
- Sensitive variable values are not returned in the response
- Both Terraform and environment variables are included

### create_variable_in_variable_set

**Function:** `create_variable_in_variable_set(varset_id: str, key: str, category: str, params: Optional[VariableSetVariableParams] = None) -> Dict[str, Any]`

**Description:** Creates a new Terraform or environment variable within a variable set.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")
- `key` (str): The variable name/key
- `category` (str): Variable category ("terraform" or "env")
- `params` (VariableSetVariableParams, optional): Additional variable parameters including value, description, HCL format, and sensitivity settings

**Returns:** JSON response with the created variable including its configuration and metadata.

**Notes:**
- Requires "write variable sets" permission
- HCL format is only valid for terraform category variables
- Variable becomes available in all workspaces/projects assigned to the variable set

### update_variable_in_variable_set

**Function:** `update_variable_in_variable_set(varset_id: str, var_id: str, params: Optional[VariableSetVariableParams] = None) -> Dict[str, Any]`

**Description:** Modifies the configuration of an existing variable within a variable set. Only specified attributes will be updated.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")
- `var_id` (str): The ID of the variable (format: "var-xxxxxxxx")
- `params` (VariableSetVariableParams, optional): Variable parameters to update including key, value, description, category, HCL format, and sensitivity

**Returns:** JSON response with the updated variable including all current settings.

**Notes:**
- Requires "write variable sets" permission
- Only specified attributes are updated; others remain unchanged
- Changes affect all workspaces using this variable set

### delete_variable_from_variable_set

**Function:** `delete_variable_from_variable_set(varset_id: str, var_id: str) -> Dict[str, Any]`

**Description:** Permanently removes a variable from a variable set. This action cannot be undone.

**Parameters:**
- `varset_id` (str): The ID of the variable set (format: "varset-xxxxxxxx")
- `var_id` (str): The ID of the variable (format: "var-xxxxxxxx")

**Returns:** Empty response with HTTP 204 status code if successful.

**Notes:**
- DESTRUCTIVE OPERATION: Requires `ENABLE_DELETE_TOOLS=true` environment variable
- Requires "write variable sets" permission
- This action cannot be undone
- Variable becomes unavailable in all workspaces using this variable set

## Common Error Scenarios

| Error | Description | Resolution |
|-------|-------------|------------|
| 401 Unauthorized | Invalid or missing authentication token | Verify TFC_TOKEN environment variable |
| 403 Forbidden | Insufficient permissions for the operation | Check user/team permissions on workspace/organization |
| 404 Not Found | Workspace, variable set, or variable not found | Verify the ID format and existence |
| 422 Unprocessable Entity | Invalid variable configuration (e.g., HCL on env variable) | Review variable parameters and constraints |
| 409 Conflict | Variable key already exists in workspace/variable set | Use a different key or update the existing variable |
```

--------------------------------------------------------------------------------
/terraform_cloud_mcp/models/runs.py:
--------------------------------------------------------------------------------

```python
"""Run models for Terraform Cloud API

This module contains models for Terraform Cloud run-related requests.
Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run
"""

from enum import Enum
from typing import List, Optional

from pydantic import Field

from .base import APIRequest


class RunOperation(str, Enum):
    """Operation options for runs in Terraform Cloud.

    Defines the different types of operations a run can perform:
    - PLAN_ONLY: Create a plan without applying changes
    - PLAN_AND_APPLY: Create a plan and apply if approved
    - SAVE_PLAN: Save the plan for later use
    - REFRESH_ONLY: Only refresh state without planning changes
    - DESTROY: Destroy all resources
    - EMPTY_APPLY: Apply even with no changes detected

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#list-runs-in-a-workspace

    See:
        docs/models/run.md for reference
    """

    PLAN_ONLY = "plan_only"
    PLAN_AND_APPLY = "plan_and_apply"
    SAVE_PLAN = "save_plan"
    REFRESH_ONLY = "refresh_only"
    DESTROY = "destroy"
    EMPTY_APPLY = "empty_apply"


class RunStatus(str, Enum):
    """Status options for runs in Terraform Cloud.

    Defines the various states a run can be in during its lifecycle,
    from initial creation through planning, policy checks, application,
    and completion or cancellation.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#list-runs-in-a-workspace

    See:
        docs/models/run.md for reference
    """

    PENDING = "pending"
    FETCHING = "fetching"
    FETCHING_COMPLETED = "fetching_completed"
    PRE_PLAN_RUNNING = "pre_plan_running"
    PRE_PLAN_COMPLETED = "pre_plan_completed"
    QUEUING = "queuing"
    PLAN_QUEUED = "plan_queued"
    PLANNING = "planning"
    PLANNED = "planned"
    COST_ESTIMATING = "cost_estimating"
    COST_ESTIMATED = "cost_estimated"
    POLICY_CHECKING = "policy_checking"
    POLICY_OVERRIDE = "policy_override"
    POLICY_SOFT_FAILED = "policy_soft_failed"
    POLICY_CHECKED = "policy_checked"
    CONFIRMED = "confirmed"
    POST_PLAN_RUNNING = "post_plan_running"
    POST_PLAN_COMPLETED = "post_plan_completed"
    PLANNED_AND_FINISHED = "planned_and_finished"
    PLANNED_AND_SAVED = "planned_and_saved"
    APPLY_QUEUED = "apply_queued"
    APPLYING = "applying"
    APPLIED = "applied"
    DISCARDED = "discarded"
    ERRORED = "errored"
    CANCELED = "canceled"
    FORCE_CANCELED = "force_canceled"


class RunSource(str, Enum):
    """Source options for runs in Terraform Cloud.

    Identifies the origin of a run:
    - TFE_UI: Created through the Terraform Cloud web interface
    - TFE_API: Created through the API
    - TFE_CONFIGURATION_VERSION: Created by uploading a configuration version

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#list-runs-in-a-workspace

    See:
        docs/models/run.md for reference
    """

    TFE_UI = "tfe-ui"
    TFE_API = "tfe-api"
    TFE_CONFIGURATION_VERSION = "tfe-configuration-version"


class RunStatusGroup(str, Enum):
    """Status group options for categorizing runs.

    Groups run statuses into categories for filtering:
    - NON_FINAL: Runs that are still in progress
    - FINAL: Runs that have reached a terminal state
    - DISCARDABLE: Runs that can be discarded

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#list-runs-in-a-workspace

    See:
        docs/models/run.md for reference
    """

    NON_FINAL = "non_final"
    FINAL = "final"
    DISCARDABLE = "discardable"


class RunVariable(APIRequest):
    """Model for run-specific variables.

    Run variables are used to provide input values for a specific run,
    which override any workspace variables for that run only.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#create-a-run

    See:
        docs/models/run.md for reference
    """

    key: str = Field(
        ...,
        # No alias needed as field name matches API field name
        description="Variable key",
        min_length=1,
        max_length=128,
    )
    value: str = Field(
        ...,
        # No alias needed as field name matches API field name
        description="Variable value",
        max_length=256,
    )


class RunListInWorkspaceRequest(APIRequest):
    """Request parameters for listing runs in a workspace.

    Used with the GET /workspaces/{workspace_id}/runs endpoint to retrieve
    and filter run data for a specific workspace.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#list-runs-in-a-workspace

    See:
        docs/models/run.md for reference
    """

    workspace_id: str = Field(
        ...,
        description="The workspace ID to list runs for",
        pattern=r"^ws-[a-zA-Z0-9]{16}$",  # Standardized workspace ID pattern
    )
    page_number: Optional[int] = Field(1, ge=1, description="Page number to fetch")
    page_size: Optional[int] = Field(
        20, ge=1, le=100, description="Number of results per page"
    )
    filter_operation: Optional[str] = Field(
        None,
        description="Filter runs by operation type, comma-separated",
        max_length=100,
    )
    filter_status: Optional[str] = Field(
        None, description="Filter runs by status, comma-separated", max_length=100
    )
    filter_source: Optional[str] = Field(
        None, description="Filter runs by source, comma-separated", max_length=100
    )
    filter_status_group: Optional[str] = Field(
        None, description="Filter runs by status group", max_length=50
    )
    filter_timeframe: Optional[str] = Field(
        None, description="Filter runs by timeframe", max_length=50
    )
    filter_agent_pool_names: Optional[str] = Field(
        None,
        description="Filter runs by agent pool names, comma-separated",
        max_length=100,
    )
    search_user: Optional[str] = Field(
        None, description="Search for runs by VCS username", max_length=100
    )
    search_commit: Optional[str] = Field(
        None, description="Search for runs by commit SHA", max_length=40
    )
    search_basic: Optional[str] = Field(
        None,
        description="Basic search across run ID, message, commit SHA, and username",
        max_length=100,
    )


class RunListInOrganizationRequest(APIRequest):
    """Request parameters for listing runs in an organization.

    These parameters map to the query parameters in the runs API.
    The endpoint returns a paginated list of runs across all workspaces in an organization,
    with options for filtering by workspace name, status, and other criteria.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#list-runs-in-an-organization

    See:
        docs/models/run.md for reference
    """

    organization: str = Field(
        ...,
        description="The organization name",
        min_length=3,
        pattern=r"^[a-z0-9][-a-z0-9_]*[a-z0-9]$",
    )
    page_number: Optional[int] = Field(1, ge=1, description="Page number to fetch")
    page_size: Optional[int] = Field(
        20, ge=1, le=100, description="Number of results per page"
    )
    filter_operation: Optional[str] = Field(
        None,
        description="Filter runs by operation type, comma-separated",
        max_length=100,
    )
    filter_status: Optional[str] = Field(
        None, description="Filter runs by status, comma-separated", max_length=100
    )
    filter_source: Optional[str] = Field(
        None, description="Filter runs by source, comma-separated", max_length=100
    )
    filter_status_group: Optional[str] = Field(
        None, description="Filter runs by status group", max_length=50
    )
    filter_timeframe: Optional[str] = Field(
        None, description="Filter runs by timeframe", max_length=50
    )
    filter_agent_pool_names: Optional[str] = Field(
        None,
        description="Filter runs by agent pool names, comma-separated",
        max_length=100,
    )
    filter_workspace_names: Optional[str] = Field(
        None,
        description="Filter runs by workspace names, comma-separated",
        max_length=250,
    )
    search_user: Optional[str] = Field(
        None, description="Search for runs by VCS username", max_length=100
    )
    search_commit: Optional[str] = Field(
        None, description="Search for runs by commit SHA", max_length=40
    )
    search_basic: Optional[str] = Field(
        None,
        description="Basic search across run ID, message, commit SHA, and username",
        max_length=100,
    )


class BaseRunRequest(APIRequest):
    """Base class for run requests with common fields.

    Common fields shared across run creation and management APIs.
    Provides field definitions and validation rules for run operations.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run

    Note:
        Consolidates common parameters for consistency across endpoints

    See:
        docs/models/run.md for reference
    """

    # Inherits model_config from APIRequest -> BaseModelConfig

    # Optional fields with their defaults
    message: Optional[str] = Field(None, description="Message to include with the run")
    auto_apply: Optional[bool] = Field(
        None,
        alias="auto-apply",
        description="Whether to auto-apply the run when planned (defaults to workspace setting)",
    )
    is_destroy: Optional[bool] = Field(
        False,
        alias="is-destroy",
        description="Whether this run should destroy all resources",
    )
    refresh: Optional[bool] = Field(
        True, description="Whether to refresh state before plan"
    )
    refresh_only: Optional[bool] = Field(
        False, alias="refresh-only", description="Whether this is a refresh-only run"
    )
    plan_only: Optional[bool] = Field(
        False,
        alias="plan-only",
        description="Whether this is a speculative, plan-only run",
    )
    allow_empty_apply: Optional[bool] = Field(
        False,
        alias="allow-empty-apply",
        description="Whether to allow apply when there are no changes",
    )
    allow_config_generation: Optional[bool] = Field(
        False,
        alias="allow-config-generation",
        description="Whether to allow generating config for imports",
    )
    target_addrs: Optional[List[str]] = Field(
        None, alias="target-addrs", description="Resource addresses to target"
    )
    replace_addrs: Optional[List[str]] = Field(
        None, alias="replace-addrs", description="Resource addresses to replace"
    )
    variables: Optional[List[RunVariable]] = Field(
        None, description="Run-specific variables"
    )
    terraform_version: Optional[str] = Field(
        None,
        alias="terraform-version",
        description="Specific Terraform version (only valid for plan-only runs)",
    )
    save_plan: Optional[bool] = Field(
        False,
        alias="save-plan",
        description="Whether to save the plan without becoming the current run",
    )
    debugging_mode: Optional[bool] = Field(
        False, alias="debugging-mode", description="Enable debug logging for this run"
    )


class RunCreateRequest(BaseRunRequest):
    """Request model for creating a Terraform Cloud run.

    Validates and structures the request according to the Terraform Cloud API
    requirements for creating runs. The model inherits common run attributes from
    BaseRunRequest and adds workspace_id as a required parameter.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#create-a-run

    Note:
        This inherits all configuration fields from BaseRunRequest
        and adds workspace_id as a required parameter.
        This model is typically used internally by the create_run tool function,
        which accepts parameters directly and constructs the request object.

    See:
        docs/models/run.md for reference
    """

    # Required fields
    workspace_id: str = Field(
        ...,
        # No alias needed as field name matches API field name
        description="The workspace ID to execute the run in (required)",
        pattern=r"^ws-[a-zA-Z0-9]{16}$",  # Standardized workspace ID pattern
    )

    # Optional fields specific to run creation
    configuration_version_id: Optional[str] = Field(
        None,
        alias="configuration-version-id",
        description="The configuration version ID to use",
        pattern=r"^cv-[a-zA-Z0-9]{16}$",
    )


class RunActionRequest(APIRequest):
    """Base request model for run actions like apply, discard, cancel, etc.

    This model provides common fields used in run action requests such as
    applying, discarding, or canceling runs. It includes the run ID and
    an optional comment field that can be included with the action.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#apply-a-run

    Note:
        This model is used for multiple run action endpoints that share the
        same basic structure but perform different operations on the run.

    See:
        docs/models/run.md for reference
    """

    run_id: str = Field(
        ...,
        # No alias needed as field name matches API field name
        description="The ID of the run to perform an action on",
        pattern=r"^run-[a-zA-Z0-9]{16}$",
    )
    comment: Optional[str] = Field(
        None,
        # No alias needed as field name matches API field name
        description="An optional comment about the run",
    )


class RunParams(BaseRunRequest):
    """Parameters for run operations without routing fields.

    This model provides all optional parameters that can be used when creating runs,
    reusing the field definitions from BaseRunRequest.

    Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#create-a-run

    Note:
        All fields are inherited from BaseRunRequest.

    See:
        docs/models/run.md for reference
    """

    # Inherits model_config and all fields from BaseRunRequest


# Response handling is implemented through raw dictionaries

```

--------------------------------------------------------------------------------
/terraform_cloud_mcp/tools/runs.py:
--------------------------------------------------------------------------------

```python
"""Run management tools for Terraform Cloud API.

This module provides tools for managing runs in Terraform Cloud.
Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run
"""

from typing import Optional

from ..api.client import api_request
from ..utils.decorators import handle_api_errors
from ..utils.payload import create_api_payload, add_relationship
from ..utils.request import query_params
from ..models.base import APIResponse
from ..models.runs import (
    RunListInWorkspaceRequest,
    RunListInOrganizationRequest,
    RunCreateRequest,
    RunActionRequest,
    RunParams,
)


@handle_api_errors
async def create_run(
    workspace_id: str,
    params: Optional[RunParams] = None,
) -> APIResponse:
    """Create a run in a workspace

    Creates a new Terraform run to trigger infrastructure changes through Terraform Cloud,
    representing a single execution of plan and apply operations. The run queues in the
    workspace and executes based on the workspace's execution mode and settings. Use this
    to deploy new infrastructure, apply configuration changes, or destroy resources.

    API endpoint: POST /runs

    Args:
        workspace_id: The workspace ID to execute the run in (format: "ws-xxxxxxxx")
        params: Optional run configuration with:
            - message: Description of the run's purpose
            - is_destroy: Whether to destroy all resources managed by the workspace
            - auto_apply: Whether to auto-apply after a successful plan
            - refresh: Whether to refresh Terraform state before planning
            - refresh_only: Only refresh the state without planning changes
            - plan_only: Create a speculative plan without applying
            - allow_empty_apply: Allow applying when there are no changes
            - target_addrs: List of resource addresses to specifically target
            - replace_addrs: List of resource addresses to force replacement
            - variables: Run-specific variables that override workspace variables
            - terraform_version: Specific Terraform version to use for this run
            - save_plan: Save the plan for later execution
            - debugging_mode: Enable extended debug logging

    Returns:
        The created run details with ID, status, configuration information,
        workspace relationship, and links to associated resources

    See:
        docs/tools/run.md for reference documentation
    """
    # Convert optional params to dictionary
    param_dict = params.model_dump(exclude_none=True) if params else {}

    # Create validated request object
    request = RunCreateRequest(workspace_id=workspace_id, **param_dict)

    # Extract variables for special handling
    variables = request.variables

    # Create API payload using utility function
    payload = create_api_payload(
        resource_type="runs",
        model=request,
        exclude_fields={"workspace_id", "variables"},  # Fields handled separately
    )

    # Add workspace relationship
    add_relationship(
        payload=payload,
        relation_name="workspace",
        resource_type="workspaces",
        resource_id=workspace_id,
    )

    # Add optional configuration version relationship
    if request.configuration_version_id:
        add_relationship(
            payload=payload,
            relation_name="configuration-version",
            resource_type="configuration-versions",
            resource_id=request.configuration_version_id,
        )

    # Transform variables to key-value format required by API
    if variables:
        payload["data"]["attributes"]["variables"] = [
            {"key": var.key, "value": var.value} for var in variables
        ]

    return await api_request("runs", method="POST", data=payload)


@handle_api_errors
async def list_runs_in_workspace(
    workspace_id: str,
    page_number: int = 1,
    page_size: int = 20,
    filter_operation: Optional[str] = None,
    filter_status: Optional[str] = None,
    filter_source: Optional[str] = None,
    filter_status_group: Optional[str] = None,
    filter_timeframe: Optional[str] = None,
    filter_agent_pool_names: Optional[str] = None,
    search_user: Optional[str] = None,
    search_commit: Optional[str] = None,
    search_basic: Optional[str] = None,
) -> APIResponse:
    """List runs in a workspace with filtering and pagination

    Retrieves run history for a specific workspace with options to filter by status,
    operation type, source, and other criteria. Useful for auditing changes, troubleshooting,
    or monitoring deployment history.

    API endpoint: GET /workspaces/{workspace_id}/runs

    Args:
        workspace_id: The workspace ID to list runs for (format: "ws-xxxxxxxx")
        page_number: Page number to fetch (default: 1)
        page_size: Number of results per page (default: 20)
        filter_operation: Filter by operation type
        filter_status: Filter by status
        filter_source: Filter by source
        filter_status_group: Filter by status group
        filter_timeframe: Filter by timeframe
        filter_agent_pool_names: Filter by agent pool names
        search_user: Search by VCS username
        search_commit: Search by commit SHA
        search_basic: Search across run ID, message, commit SHA, and username

    Returns:
        List of runs with metadata, status info, and pagination details

    See:
        docs/tools/run.md for reference documentation
    """
    # Create request using Pydantic model for validation
    request = RunListInWorkspaceRequest(
        workspace_id=workspace_id,
        page_number=page_number,
        page_size=page_size,
        filter_operation=filter_operation,
        filter_status=filter_status,
        filter_source=filter_source,
        filter_status_group=filter_status_group,
        filter_timeframe=filter_timeframe,
        filter_agent_pool_names=filter_agent_pool_names,
        search_user=search_user,
        search_commit=search_commit,
        search_basic=search_basic,
    )

    # Use the unified query params utility function
    params = query_params(request)

    # Make API request
    return await api_request(
        f"workspaces/{workspace_id}/runs", method="GET", params=params
    )


@handle_api_errors
async def list_runs_in_organization(
    organization: str,
    page_number: int = 1,
    page_size: int = 20,
    filter_operation: Optional[str] = None,
    filter_status: Optional[str] = None,
    filter_source: Optional[str] = None,
    filter_status_group: Optional[str] = None,
    filter_timeframe: Optional[str] = None,
    filter_agent_pool_names: Optional[str] = None,
    filter_workspace_names: Optional[str] = None,
    search_user: Optional[str] = None,
    search_commit: Optional[str] = None,
    search_basic: Optional[str] = None,
) -> APIResponse:
    """List runs across all workspaces in an organization

    Retrieves run history across all workspaces in an organization with powerful filtering.
    Useful for organization-wide auditing, monitoring deployments across teams, or finding
    specific runs by commit or author.

    API endpoint: GET /organizations/{organization}/runs

    Args:
        organization: The organization name
        page_number: Page number to fetch (default: 1)
        page_size: Number of results per page (default: 20)
        filter_operation: Filter by operation type
        filter_status: Filter by status
        filter_source: Filter by source
        filter_status_group: Filter by status group
        filter_timeframe: Filter by timeframe
        filter_agent_pool_names: Filter by agent pool names
        filter_workspace_names: Filter by workspace names
        search_user: Search by VCS username
        search_commit: Search by commit SHA
        search_basic: Basic search across run attributes

    Returns:
        List of runs across workspaces with metadata and pagination details

    See:
        docs/tools/run.md for reference documentation
    """
    # Create request using Pydantic model for validation
    request = RunListInOrganizationRequest(
        organization=organization,
        page_number=page_number,
        page_size=page_size,
        filter_operation=filter_operation,
        filter_status=filter_status,
        filter_source=filter_source,
        filter_status_group=filter_status_group,
        filter_timeframe=filter_timeframe,
        filter_agent_pool_names=filter_agent_pool_names,
        filter_workspace_names=filter_workspace_names,
        search_user=search_user,
        search_commit=search_commit,
        search_basic=search_basic,
    )

    # Use the unified query params utility function
    params = query_params(request)

    # Make API request
    return await api_request(
        f"organizations/{organization}/runs", method="GET", params=params
    )


@handle_api_errors
async def get_run_details(run_id: str) -> APIResponse:
    """Get detailed information about a specific run

    Retrieves comprehensive information about a run including its current status,
    plan output, and relationship to other resources. Use to check run progress or results.

    API endpoint: GET /runs/{run_id}

    Args:
        run_id: The ID of the run to retrieve details for (format: "run-xxxxxxxx")

    Returns:
        Complete run details including status, plan, and relationships

    See:
        docs/tools/run.md for reference documentation
    """
    # Make API request
    return await api_request(f"runs/{run_id}", method="GET")


@handle_api_errors
async def apply_run(run_id: str, comment: str = "") -> APIResponse:
    """Apply a run that is paused waiting for confirmation after a plan

    Confirms and executes the apply phase for a run that has completed planning and is
    waiting for approval. Use this when you've reviewed the plan output and want to
    apply the proposed changes to your infrastructure.

    API endpoint: POST /runs/{run_id}/actions/apply

    Args:
        run_id: The ID of the run to apply (format: "run-xxxxxxxx")
        comment: An optional comment explaining the reason for applying the run

    Returns:
        Run details with updated status information and confirmation of the apply action
        including timestamp information and any comment provided

    See:
        docs/tools/run.md for reference documentation
    """
    request = RunActionRequest(run_id=run_id, comment=comment)

    # Create payload if comment is provided
    payload = {}
    if request.comment:
        payload = {"comment": request.comment}

    # Make API request
    return await api_request(
        f"runs/{run_id}/actions/apply", method="POST", data=payload
    )


@handle_api_errors
async def discard_run(run_id: str, comment: str = "") -> APIResponse:
    """Discard a run that is paused waiting for confirmation

    Cancels a run without applying its changes, typically used when the plan
    shows undesired changes or after reviewing and rejecting a plan. This action
    removes the run from the queue and unlocks the workspace for new runs.

    API endpoint: POST /runs/{run_id}/actions/discard

    Args:
        run_id: The ID of the run to discard (format: "run-xxxxxxxx")
        comment: An optional explanation for why the run was discarded

    Returns:
        Run status update with discarded state information, timestamp of the
        discard action, and user information

    See:
        docs/tools/run.md for reference documentation
    """
    request = RunActionRequest(run_id=run_id, comment=comment)

    # Create payload if comment is provided
    payload = {}
    if request.comment:
        payload = {"comment": request.comment}

    # Make API request
    return await api_request(
        f"runs/{run_id}/actions/discard", method="POST", data=payload
    )


@handle_api_errors
async def cancel_run(run_id: str, comment: str = "") -> APIResponse:
    """Cancel a run that is currently planning or applying

    Gracefully stops an in-progress run during planning or applying phases. Use this
    when you need to stop a run that's taking too long, consuming too many resources,
    or needs to be stopped for any reason. The operation attempts to cleanly terminate
    the run by sending an interrupt signal.

    API endpoint: POST /runs/{run_id}/actions/cancel

    Args:
        run_id: The ID of the run to cancel (format: "run-xxxxxxxx")
        comment: An optional explanation for why the run was canceled

    Returns:
        Run status update with canceled state, timestamp of cancellation,
        and any provided comment in the response metadata

    See:
        docs/tools/run.md for reference documentation
    """
    request = RunActionRequest(run_id=run_id, comment=comment)

    # Create payload if comment is provided
    payload = {}
    if request.comment:
        payload = {"comment": request.comment}

    # Make API request
    return await api_request(
        f"runs/{run_id}/actions/cancel", method="POST", data=payload
    )


@handle_api_errors
async def force_cancel_run(run_id: str, comment: str = "") -> APIResponse:
    """Forcefully cancel a run immediately

    Immediately terminates a run that hasn't responded to a normal cancel request.
    Use this as a last resort when a run is stuck and not responding to regular
    cancellation. This action bypasses the graceful shutdown process and forces
    the workspace to be unlocked.

    API endpoint: POST /runs/{run_id}/actions/force-cancel

    Args:
        run_id: The ID of the run to force cancel (format: "run-xxxxxxxx")
        comment: An optional explanation for why the run was force canceled

    Returns:
        Run status update confirming forced cancellation with timestamp,
        user information, and workspace unlock status

    See:
        docs/tools/run.md for reference documentation
    """
    request = RunActionRequest(run_id=run_id, comment=comment)

    # Create payload if comment is provided
    payload = {}
    if request.comment:
        payload = {"comment": request.comment}

    # Make API request
    return await api_request(
        f"runs/{run_id}/actions/force-cancel", method="POST", data=payload
    )


@handle_api_errors
async def force_execute_run(run_id: str) -> APIResponse:
    """Forcefully execute a run by canceling all prior runs

    Prioritizes a specific run by canceling other queued runs to unlock the workspace,
    equivalent to clicking "Run this plan now" in the UI. Use this when a run is
    stuck in the pending queue but needs immediate execution due to urgency or
    priority over other queued runs.

    API endpoint: POST /runs/{run_id}/actions/force-execute

    Args:
        run_id: The ID of the run to execute (format: "run-xxxxxxxx")

    Returns:
        Status update confirming the run has been promoted to active status,
        with information about which runs were canceled to allow execution

    See:
        docs/tools/run.md for reference documentation
    """
    # Make API request
    return await api_request(f"runs/{run_id}/actions/force-execute", method="POST")

```

--------------------------------------------------------------------------------
/terraform_cloud_mcp/tools/workspaces.py:
--------------------------------------------------------------------------------

```python
"""Workspace management tools for Terraform Cloud MCP

This module implements the workspace-related endpoints of the Terraform Cloud API.
Reference: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspaces
"""

import logging
from typing import Optional

from ..api.client import api_request
from ..utils.decorators import handle_api_errors
from ..utils.payload import create_api_payload
from ..utils.request import query_params
from ..models.base import APIResponse
from ..models.workspaces import (
    WorkspaceCreateRequest,
    WorkspaceUpdateRequest,
    WorkspaceListRequest,
    WorkspaceParams,
    DataRetentionPolicyRequest,
)


@handle_api_errors
async def create_workspace(
    organization: str, name: str, params: Optional[WorkspaceParams] = None
) -> APIResponse:
    """Create a new workspace in an organization.

    Creates a new Terraform Cloud workspace which serves as an isolated environment
    for managing infrastructure. Workspaces contain variables, state files, and run
    histories for a specific infrastructure configuration.

    API endpoint: POST /organizations/{organization}/workspaces

    Args:
        organization: The name of the organization
        name: The name to give the workspace

        params: Additional workspace parameters (optional):
            - description: Human-readable description of the workspace
            - execution_mode: How Terraform runs are executed (remote, local, agent)
            - terraform_version: Version of Terraform to use (default: latest)
            - working_directory: Subdirectory to use when running Terraform
            - vcs_repo: Version control repository configuration
            - auto_apply: Whether to automatically apply successful plans
            - file_triggers_enabled: Whether file changes trigger runs
            - trigger_prefixes: Directories that trigger runs when changed
            - trigger_patterns: Glob patterns that trigger runs when files match
            - allow_destroy_plan: Whether to allow destruction plans
            - auto_apply_run_trigger: Whether to auto-apply changes from run triggers

    Returns:
        The created workspace data including configuration, settings and metadata

    See:
        docs/tools/workspace.md for reference documentation
    """
    param_dict = params.model_dump(exclude_none=True) if params else {}
    request = WorkspaceCreateRequest(organization=organization, name=name, **param_dict)

    payload = create_api_payload(
        resource_type="workspaces", model=request, exclude_fields={"organization"}
    )

    return await api_request(
        f"organizations/{organization}/workspaces", method="POST", data=payload
    )


@handle_api_errors
async def update_workspace(
    organization: str, workspace_name: str, params: Optional[WorkspaceParams] = None
) -> APIResponse:
    """Update an existing workspace.

    Modifies the settings of a Terraform Cloud workspace. This can be used to change
    attributes like execution mode, VCS repository settings, description, or any other
    workspace configuration options. Only specified attributes will be updated;
    unspecified attributes remain unchanged.

    API endpoint: PATCH /organizations/{organization}/workspaces/{workspace_name}

    Args:
        organization: The name of the organization that owns the workspace
        workspace_name: The name of the workspace to update

        params: Workspace parameters to update (optional):
            - name: New name for the workspace (if renaming)
            - description: Human-readable description of the workspace
            - execution_mode: How Terraform runs are executed (remote, local, agent)
            - terraform_version: Version of Terraform to use
            - working_directory: Subdirectory to use when running Terraform
            - vcs_repo: Version control repository configuration (oauth-token-id, identifier)
            - auto_apply: Whether to automatically apply successful plans
            - file_triggers_enabled: Whether file changes trigger runs
            - trigger_prefixes: Directories that trigger runs when changed
            - trigger_patterns: Glob patterns that trigger runs when files match
            - allow_destroy_plan: Whether to allow destruction plans
            - auto_apply_run_trigger: Whether to auto-apply changes from run triggers

    Returns:
        The updated workspace with all current settings and configuration

    See:
        docs/tools/workspace.md for reference documentation
    """
    # Extract parameters from the params object if provided
    param_dict = params.model_dump(exclude_none=True) if params else {}

    # Create request using Pydantic model
    request = WorkspaceUpdateRequest(
        organization=organization, workspace_name=workspace_name, **param_dict
    )

    # Create API payload using utility function
    payload = create_api_payload(
        resource_type="workspaces",
        model=request,
        exclude_fields={"organization", "workspace_name"},
    )

    # Log payload for debugging
    logger = logging.getLogger(__name__)
    logger.debug(f"Update workspace payload: {payload}")

    # Make API request
    response = await api_request(
        f"organizations/{organization}/workspaces/{workspace_name}",
        method="PATCH",
        data=payload,
    )

    # Log response for debugging
    logger.debug(f"Update workspace response: {response}")

    return response


@handle_api_errors
async def list_workspaces(
    organization: str,
    page_number: int = 1,
    page_size: int = 20,
    search: Optional[str] = None,
) -> APIResponse:
    """List workspaces in an organization.

    Retrieves a paginated list of all workspaces in a Terraform Cloud organization.
    Results can be filtered using a search string to find specific workspaces by name.
    Use this tool to discover existing workspaces, check workspace configurations,
    or find specific workspaces by partial name match.

    API endpoint: GET /organizations/{organization}/workspaces

    Args:
        organization: The name of the organization to list workspaces from
        page_number: The page number to return (default: 1)
        page_size: The number of items per page (default: 20, max: 100)
        search: Optional search string to filter workspaces by name

    Returns:
        Paginated list of workspaces with their configuration settings and metadata

    See:
        docs/tools/workspace.md for reference documentation
    """
    # Create request using Pydantic model for validation
    request = WorkspaceListRequest(
        organization=organization,
        page_number=page_number,
        page_size=page_size,
        search=search,
    )

    params = query_params(request)

    return await api_request(
        f"organizations/{organization}/workspaces", method="GET", params=params
    )


@handle_api_errors
async def delete_workspace(organization: str, workspace_name: str) -> APIResponse:
    """Delete a workspace.

    Permanently deletes a Terraform Cloud workspace and all its resources including
    state versions, run history, and configuration versions. This action cannot be undone.

    WARNING: This is a destructive operation. For workspaces that have active resources,
    consider running a destroy plan first or use safe_delete_workspace instead.

    API endpoint: DELETE /organizations/{organization}/workspaces/{workspace_name}

    Args:
        organization: The name of the organization that owns the workspace
        workspace_name: The name of the workspace to delete

    Returns:
        Success message with no content (HTTP 204) if successful
        Error response with explanation if the workspace cannot be deleted

    See:
        docs/tools/workspace.md for reference documentation
    """
    # Make API request
    return await api_request(
        f"organizations/{organization}/workspaces/{workspace_name}",
        method="DELETE",
    )


@handle_api_errors
async def safe_delete_workspace(organization: str, workspace_name: str) -> APIResponse:
    """Safely delete a workspace by first checking if it can be deleted.

    Initiates a safe delete operation which checks if the workspace has resources
    before deleting it. This is a safer alternative to delete_workspace as it prevents
    accidental deletion of workspaces with active infrastructure.

    The operation follows these steps:
    1. Checks if the workspace has any resources
    2. If no resources exist, deletes the workspace
    3. If resources exist, returns an error indicating the workspace cannot be safely deleted

    API endpoint: POST /organizations/{organization}/workspaces/{workspace_name}/actions/safe-delete

    Args:
        organization: The name of the organization that owns the workspace
        workspace_name: The name of the workspace to delete

    Returns:
        Status of the safe delete operation including:
        - Success response if deletion was completed
        - Error with details if workspace has resources and cannot be safely deleted
        - List of resources that would be affected by deletion (if applicable)

    See:
        docs/tools/workspace.md for reference documentation
    """
    # Make API request
    return await api_request(
        f"organizations/{organization}/workspaces/{workspace_name}/actions/safe-delete",
        method="POST",
    )


@handle_api_errors
async def lock_workspace(workspace_id: str, reason: str = "") -> APIResponse:
    """Lock a workspace.

    Locks a workspace to prevent runs from being queued. This is useful when you want
    to prevent changes to infrastructure while performing maintenance or making manual
    adjustments. Locking a workspace does not affect currently running plans or applies.

    API endpoint: POST /workspaces/{workspace_id}/actions/lock

    Args:
        workspace_id: The ID of the workspace to lock (format: "ws-xxxxxxxx")
        reason: Optional reason for locking

    Returns:
        The workspace with updated lock status and related metadata

    See:
        docs/tools/workspace.md for reference documentation
    """
    payload = {}
    if reason:
        payload = {"reason": reason}
    return await api_request(
        f"workspaces/{workspace_id}/actions/lock", method="POST", data=payload
    )


@handle_api_errors
async def unlock_workspace(workspace_id: str) -> APIResponse:
    """Unlock a workspace.

    Removes the lock from a workspace, allowing runs to be queued. This enables
    normal operation of the workspace after it was previously locked.

    API endpoint: POST /workspaces/{workspace_id}/actions/unlock

    Args:
        workspace_id: The ID of the workspace to unlock (format: "ws-xxxxxxxx")

    Returns:
        The workspace with updated lock status and related metadata

    See:
        docs/tools/workspace.md for reference documentation
    """
    return await api_request(f"workspaces/{workspace_id}/actions/unlock", method="POST")


@handle_api_errors
async def force_unlock_workspace(workspace_id: str) -> APIResponse:
    """Force unlock a workspace. This should be used with caution.

    Forces a workspace to unlock even when the normal unlock process isn't possible.
    This is typically needed when a run has orphaned a lock or when the user who locked
    the workspace is unavailable. This operation requires admin privileges on the workspace.

    WARNING: Forcing an unlock can be dangerous if the workspace is legitimately locked
    for active operations. Only use this when you are certain it's safe to unlock.

    API endpoint: POST /workspaces/{workspace_id}/actions/force-unlock

    Args:
        workspace_id: The ID of the workspace to force unlock (format: "ws-xxxxxxxx")

    Returns:
        The workspace with updated lock status and related metadata

    See:
        docs/tools/workspace.md for reference documentation
    """
    # Make API request
    return await api_request(
        f"workspaces/{workspace_id}/actions/force-unlock", method="POST"
    )


@handle_api_errors
async def set_data_retention_policy(workspace_id: str, days: int) -> APIResponse:
    """Set a data retention policy for a workspace.

    Creates or updates a data retention policy that determines how long Terraform Cloud
    keeps run history and state files for a workspace. This can be used to comply with
    data retention requirements or to reduce resource usage.

    API endpoint: POST /workspaces/{workspace_id}/relationships/data-retention-policy

    Args:
        workspace_id: The ID of the workspace (format: "ws-xxxxxxxx")
        days: Number of days to retain data

    Returns:
        The created data retention policy with configuration details and timestamps

    See:
        docs/tools/workspace.md for reference documentation
    """
    # Create request using Pydantic model
    request = DataRetentionPolicyRequest(workspace_id=workspace_id, days=days)

    # Create API payload using utility function
    payload = create_api_payload(
        resource_type="data-retention-policy",
        model=request,
        exclude_fields={"workspace_id"},
    )

    # Make API request
    return await api_request(
        f"workspaces/{workspace_id}/relationships/data-retention-policy",
        method="POST",
        data=payload,
    )


@handle_api_errors
async def get_data_retention_policy(workspace_id: str) -> APIResponse:
    """Get the data retention policy for a workspace.

    Retrieves the current data retention policy for a workspace, which defines how long
    Terraform Cloud keeps run history and state files before automatic removal.

    API endpoint: GET /workspaces/{workspace_id}/relationships/data-retention-policy

    Args:
        workspace_id: The ID of the workspace (format: "ws-xxxxxxxx")

    Returns:
        The data retention policy with configuration details and timestamps

    See:
        docs/tools/workspace.md for reference documentation
    """
    # Make API request
    return await api_request(
        f"workspaces/{workspace_id}/relationships/data-retention-policy", method="GET"
    )


@handle_api_errors
async def delete_data_retention_policy(workspace_id: str) -> APIResponse:
    """Delete the data retention policy for a workspace.

    Removes the data retention policy from a workspace, reverting to the default behavior
    of retaining all data indefinitely. This is useful when you no longer want to automatically
    remove historical data after a certain period.

    API endpoint: DELETE /workspaces/{workspace_id}/relationships/data-retention-policy

    Args:
        workspace_id: The ID of the workspace (format: "ws-xxxxxxxx")

    Returns:
        Empty response with HTTP 204 status code indicating successful deletion

    See:
        docs/tools/workspace.md for reference documentation
    """
    # Make API request
    return await api_request(
        f"workspaces/{workspace_id}/relationships/data-retention-policy",
        method="DELETE",
    )


@handle_api_errors
async def get_workspace_details(
    workspace_id: str = "", organization: str = "", workspace_name: str = ""
) -> APIResponse:
    """Get details for a specific workspace, identified either by ID or by org name and workspace name.

    Retrieves comprehensive information about a workspace including its configuration,
    VCS settings, execution mode, and other attributes. This is useful for checking
    workspace settings before operations or determining the current state of a workspace.

    The workspace can be identified either by its ID directly, or by the combination
    of organization name and workspace name.

    API endpoint:
    - GET /workspaces/{workspace_id} (when using workspace_id)
    - GET /organizations/{organization}/workspaces/{workspace_name} (when using org+name)

    Args:
        workspace_id: The ID of the workspace (format: "ws-xxxxxxxx")
        organization: The name of the organization (required if workspace_id not provided)
        workspace_name: The name of the workspace (required if workspace_id not provided)

    Returns:
        Comprehensive workspace details including settings, configuration and status

    See:
        docs/tools/workspace.md for reference documentation
    """
    # Ensure we have either workspace_id OR both organization and workspace_name
    if not workspace_id and not (organization and workspace_name):
        raise ValueError(
            "Either workspace_id OR both organization and workspace_name must be provided"
        )

    # Determine API path based on provided parameters
    if workspace_id:
        path = f"workspaces/{workspace_id}"
    else:
        path = f"organizations/{organization}/workspaces/{workspace_name}"

    # Make API request
    return await api_request(path, method="GET")

```

--------------------------------------------------------------------------------
/docs/DEVELOPMENT.md:
--------------------------------------------------------------------------------

```markdown
# Terraform Cloud MCP Development Guide

This document outlines the development guidelines, code standards, and best practices for the Terraform Cloud MCP project.

## Getting Started

### Development Setup

```bash
# Clone the repository
git clone https://github.com/severity1/terraform-cloud-mcp.git
cd terraform-cloud-mcp

# Create virtual environment and activate it
uv venv
source .venv/bin/activate

# Install in development mode (editable)
uv pip install -e .

# Install development dependencies
uv pip install black mypy pydantic ruff
```

### Build & Run Commands

- Setup: `uv pip install -e .` (install with latest changes)
- Install dev deps: `uv pip install black mypy pydantic ruff`
- Format: `uv pip install black && uv run -m black .` 
- Type check: `uv pip install mypy && uv run -m mypy .`
- Lint: `uv pip install ruff && uv run -m ruff check .`
- Fix lint issues: `uv pip install ruff && uv run -m ruff check --fix .`

### Development With Claude Integrations

#### Adding to Claude Code (Development Mode)

```bash
# Add to Claude Code with your development path and token
claude mcp add -e TFC_TOKEN=YOUR_TF_TOKEN -s user terraform-cloud-mcp -- "$(pwd)/terraform_cloud_mcp/server.py"

# To use a self-hosted Terraform Enterprise instance:
# claude mcp add -e TFC_TOKEN=YOUR_TF_TOKEN -e TFC_ADDRESS=https://terraform.example.com -s user terraform-cloud-mcp -- "$(pwd)/terraform_cloud_mcp/server.py"
```

#### Adding to Claude Desktop (Development Mode)

Create a `claude_desktop_config.json` configuration file:
- mac: ~/Library/Application Support/Claude/claude_desktop_config.json
- win: %APPDATA%\Claude\claude_desktop_config.json

```json
{
  "mcpServers": {
    "terraform-cloud-mcp": {
      "command": "/path/to/uv", # Get this by running: `which uv`
      "args": [
        "--directory",
        "/path/to/your/terraform-cloud-mcp", # Full path to this project
        "run",
        "terraform_cloud_mcp/server.py"
      ],
      "env": {
        "TFC_TOKEN": "your_terraform_cloud_token", # Your actual TF Cloud token
        "TFC_ADDRESS": "https://app.terraform.io" # Optional, change for self-hosted TFE
      }
    }
  }
}
```

## Core Principles

- **KISS (Keep It Simple, Stupid)**: Favor simple, maintainable solutions over complex ones
- **DRY (Don't Repeat Yourself)**: Use utility functions for common patterns
- **Consistency**: Follow established patterns throughout the codebase
- **Type Safety**: Use proper typing and validation everywhere
- **Documentation**: All code should be well-documented with standardized formats
- **Testability**: Write code that can be easily tested
- **Modularity**: Keep components focused and decoupled

## Code Organization

```
terraform_cloud_mcp/
├── api/                # API client and core request handling
│   ├── __init__.py
│   └── client.py       # Core API client with error handling
├── models/             # Pydantic data models for validation
│   ├── __init__.py
│   ├── account.py      # Account-related models
│   ├── applies.py      # Apply-related models
│   ├── assessment_results.py # Assessment results models
│   ├── base.py         # Base model classes and shared types
│   ├── cost_estimates.py # Cost estimation models
│   ├── organizations.py # Organization models
│   ├── plans.py        # Plan-related models
│   ├── projects.py     # Project management models
│   ├── runs.py         # Run management models
│   └── workspaces.py   # Workspace management models
├── tools/              # Tool implementations exposed via MCP
│   ├── __init__.py
│   ├── account.py      # Account management tools
│   ├── applies.py      # Apply management tools
│   ├── assessment_results.py # Assessment results tools
│   ├── cost_estimates.py # Cost estimation tools
│   ├── organizations.py # Organization management tools
│   ├── plans.py        # Plan management tools
│   ├── projects.py     # Project management tools
│   ├── runs.py         # Run management tools
│   └── workspaces.py   # Workspace management tools
├── utils/              # Shared utilities
│   ├── __init__.py
│   ├── decorators.py   # Error handling decorators
│   ├── filters.py      # Response filtering for token optimization
│   ├── payload.py      # JSON:API payload utilities
│   └── request.py      # Request parameter utilities
└── server.py           # MCP server entry point
```

## Code Style Guidelines

### General Guidelines

- **Imports**: stdlib → third-party → local, alphabetically within groups
- **Formatting**: Black, 100 char line limit
- **Types**: Type hints everywhere, Pydantic models for validation
- **Naming**: snake_case (functions/vars), PascalCase (classes), UPPER_CASE (constants)
- **Error handling**: Use `handle_api_errors` decorator from `terraform_cloud_mcp/utils/decorators.py`
- **Audit-safe filtering**: Automatic filtering system uses conservative 5-15% token reduction while preserving 100% audit compliance - all user accountability, security, and change tracking data preserved
- **Async pattern**: All API functions should be async, using httpx
- **Security**: Never log tokens, validate all inputs, redact sensitive data
- **MCP Tool Registration**: Follow minimal pattern in `server.py`:
  - Use simple `mcp.tool()(function_name)` for standard operations
  - Use `mcp.tool(enabled=False)(function_name)` for dangerous delete operations

## Pydantic Model Standards

### Pattern Principles
- **Validation First**: Use Pydantic to validate all input parameters
- **Explicit Aliases**: Use field aliases for API compatibility (`kebab-case` to `snake_case`)
- **Base Models**: Extend from common base classes
- **No Response Models**: Use `APIResponse` type alias (`Dict[str, Any]`) for responses

### Model Structure

1. **Base Classes**:
   See the base classes defined in `terraform_cloud_mcp/models/base.py`:
   - `BaseModelConfig` - Core configuration for all models
   - `APIRequest` - Base class for all API requests
   - `APIResponse` type alias

2. **Request Models**:
   For examples of request models with field aliases, see:
   - `VcsRepoConfig` in `terraform_cloud_mcp/models/workspaces.py`
   - `WorkspaceCreateRequest` in `terraform_cloud_mcp/models/workspaces.py`
   - `WorkspaceListRequest` in `terraform_cloud_mcp/models/workspaces.py`

3. **Enum Classes**:
   For enum implementation examples, see:
   - `ExecutionMode` in `terraform_cloud_mcp/models/base.py`
   - Status enums in `terraform_cloud_mcp/models/runs.py`

### Implementation Pattern

1. **Tool Implementation**:
   See the implementation pattern in `terraform_cloud_mcp/tools/workspaces.py`:
   - `create_workspace` function for a complete implementation example
   - `update_workspace` function for updating existing resources
   - `list_workspaces` function for listing resources with pagination
   - Other CRUD operations in workspace management tools

### Pydantic Best Practices

1. **Use Field Aliases** for API compatibility:
   See the `execution_mode` field in `terraform_cloud_mcp/models/workspaces.py` (class `BaseWorkspaceRequest`)

2. **Use Field Validators** for constraints:
   See the `page_size` field in `terraform_cloud_mcp/models/workspaces.py` (class `WorkspaceListRequest`) 

3. **Use Description** for clarity:
   See the `description` field in `terraform_cloud_mcp/models/workspaces.py` (class `BaseWorkspaceRequest`)

4. **Use Enums** for constrained choices:
   See `ExecutionMode` enum in `terraform_cloud_mcp/models/base.py`

5. **Parameter Inheritance** for common parameters:
   See the class hierarchy in `terraform_cloud_mcp/models/workspaces.py`:
   - `BaseWorkspaceRequest` defines common fields
   - `WorkspaceCreateRequest` extends it with required fields
   - `WorkspaceUpdateRequest` adds routing fields
   - `WorkspaceParams` provides a parameter object without routing fields

## Utility Functions

### JSON:API Payload Utilities
To ensure consistent handling of API payloads, use the utility functions from `terraform_cloud_mcp/utils/payload.py`:

1. **create_api_payload**:
   See implementation in `terraform_cloud_mcp/utils/payload.py` and example usage in `create_workspace` function in `terraform_cloud_mcp/tools/workspaces.py`

2. **add_relationship**:
   See implementation in `terraform_cloud_mcp/utils/payload.py` and usage examples in `terraform_cloud_mcp/tools/runs.py`

### Request Parameter Utilities
For handling pagination and request parameters, see `terraform_cloud_mcp/utils/request.py` and its usage in list operations like `list_workspaces` in `terraform_cloud_mcp/tools/workspaces.py`

## Documentation Standards

### Documentation Principles
- **Essential Information**: Focus on what's needed without verbosity
- **Completeness**: Provide enough context to understand usage
- **External References**: Move examples to dedicated documentation files
- **Agent-Friendly**: Include sufficient context for AI agents/LLMs

### Model Classes

See `VcsRepoConfig` class in `terraform_cloud_mcp/models/workspaces.py` for proper model documentation

For derived classes that inherit from a base class, see `WorkspaceCreateRequest` in `terraform_cloud_mcp/models/workspaces.py` as an example of how to document inheritance

### Tool Functions

See `create_workspace` function in `terraform_cloud_mcp/tools/workspaces.py` for proper tool function documentation including:
- Purpose description
- API endpoint reference
- Parameter documentation
- Return value description
- External documentation references

### Utility Functions

See utility functions in `terraform_cloud_mcp/utils/payload.py` and `terraform_cloud_mcp/utils/request.py` for examples of properly documented helper functions

### Documentation Structure

Documentation is organized in dedicated markdown files:

```
docs/
  models/             # Documentation for Pydantic models
    account.md
    apply.md
    assessment_result.md
    cost_estimate.md
    organization.md
    plan.md
    project.md
    run.md
    workspace.md
  tools/              # Reference documentation for MCP tools
    account.md
    apply.md
    assessment_results.md
    cost_estimate.md
    organization.md
    plan.md
    project.md
    run.md
    workspace.md
  conversations/      # Example conversations using the tools
    account.md
    apply-management-conversation.md
    assessment-results-conversation.md
    cost-estimate-conversation.md
    organization-entitlements-conversation.md
    organizations-management-conversation.md
    plan-management-conversation.md
    project-management-conversation.md
    runs-management-conversation.md
    workspace-management-conversation.md
```

#### Tool Documentation Format

Each tool documentation file should follow this structure:

```markdown
# Module Name Tools

Brief introduction about the module's purpose.

## Overview

Detailed explanation of the functionality and concepts.

## API Reference

Links to relevant Terraform Cloud API documentation:
- [API Section 1](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/section)
- [API Section 2](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/section)

## Tools Reference

### function_name

**Function:** `function_name(param1: type, param2: type) -> ReturnType`

**Description:** Explanation of what the function does.

**Parameters:**
- `param1` (type): Parameter description
- `param2` (type): Parameter description

**Returns:** Description of return value structure

**Notes:**
- Important usage information
- Related functionality
- Permissions required

**Common Error Scenarios:**

| Error | Cause | Solution |
|-------|-------|----------|
| 404   | Resource not found | Verify ID and permissions |
| 422   | Invalid parameters | Ensure values match required format |
```

## Code Commenting Standards

The KISS (Keep It Simple, Stupid) principle applies to comments as much as code. Comments should be minimal, precise, and focused only on what's not obvious from the code itself.

### Comment Only When Necessary

Add comments only in these essential situations:

1. **Non-obvious "Why"**: Explain reasoning that isn't evident from reading the code
2. **Complex Logic**: Brief explanation of multi-step transformations or algorithms
3. **Edge Cases**: Why special handling is needed for boundary conditions
4. **Security Considerations**: Rationale for security measures (without exposing vulnerabilities)
5. **API Requirements**: Explanations of why code conforms to specific API requirements

### Avoid Unnecessary Comments

1. **No "What" Comments**: Don't describe what the code does when it's self-evident
2. **No Redundant Information**: Don't repeat documentation that exists in docstrings
3. **No Commented-Out Code**: Delete unused code rather than commenting it out
4. **No Obvious Comments**: Don't state the obvious (e.g., "Increment counter")

### Effective Comment Examples

#### For Complex Transformations
```python
# Transform variables array to required API format with key-value pairs
variables_array = [{"key": var.key, "value": var.value} for var in variables]
```

#### For Error Handling
```python
# Return standardized success response for 204 No Content to ensure consistent interface
if response.status_code == 204:
    return {"status": "success"}
```

#### For Security Measures
```python
# Redact token from error message to prevent credential exposure
error_message = error_message.replace(token, "[REDACTED]")
```

#### For Performance Considerations
```python
# Use exclude_unset to prevent default values from overriding server defaults
request_data = data.model_dump(exclude_unset=True)
```

## Quality Assurance Protocol

### Mandatory Quality Check Sequence

After ANY code changes, run these commands in exact order:

1. **`uv run -m ruff check --fix .`** - Fix linting issues automatically
2. **`uv run -m black .`** - Format code consistently  
3. **`uv run -m mypy .`** - Verify type safety
4. **Manual Testing** - Test basic tool functionality (see methodology below)
5. **Documentation Completeness** - Verify using checklist below

**IMPORTANT**: Fix all issues at each step before proceeding to the next step.

### Manual Testing Methodology

For each new tool, test these scenarios in order:

1. **Happy Path**: Test with valid, typical parameters
2. **Edge Cases**: Test with boundary values, empty strings, None values  
3. **Error Cases**: Test with invalid IDs, missing permissions, malformed data
4. **Integration**: Test with related tools in realistic workflows

### Documentation Completeness Checklist

- [ ] Function docstring includes API endpoint reference
- [ ] Parameter descriptions include formats and constraints
- [ ] Return value description explains structure and key fields
- [ ] docs/tools/ entry created with function signature and examples
- [ ] docs/models/ entry created if new models added
- [ ] docs/conversations/ updated with realistic usage scenario
- [ ] Cross-references between all documentation layers verified
- [ ] All links in documentation are valid and accessible

### Quality Standards

- **Code Coverage**: All new functions must have comprehensive docstrings
- **Type Safety**: All parameters and return values must have type hints
- **Error Handling**: All tools must use @handle_api_errors decorator
- **Security**: No tokens or sensitive data in logs or error messages
- **Consistency**: New code must follow established patterns in existing codebase

## Enhanced Code Style Guidelines

### Core Principles
- **KISS Principle**: Keep It Simple, Stupid. Favor simple, maintainable solutions over complex ones.
- **DRY Principle**: Don't Repeat Yourself. Use utility functions for common patterns.
- **Imports**: stdlib → third-party → local, alphabetically within groups
- **Formatting**: Black, 100 char line limit
- **Types**: Type hints everywhere, Pydantic models for validation
- **Naming**: snake_case (functions/vars), PascalCase (classes), UPPER_CASE (constants)
- **Error handling**: Use `handle_api_errors` decorator from `terraform_cloud_mcp/utils/decorators.py`
- **Async pattern**: All API functions should be async, using httpx
- **Security**: Never log tokens, validate all inputs, redact sensitive data

### Pydantic Patterns
See `terraform_cloud_mcp/models/workspaces.py` for reference implementation:
- Use `BaseModelConfig` base class for common configuration
- Use `APIRequest` for request validation
- Define explicit model classes for parameter objects (e.g., `WorkspaceParams`)
- Use `params` parameter instead of `**kwargs` in tool functions
- Use explicit field aliases (e.g., `alias="kebab-case-name"`) for API field mapping
- Type API responses as `APIResponse` (alias for `Dict[str, Any]`)

### Utility Functions
Use common utilities for repetitive patterns:
- `create_api_payload()` from `utils/payload.py` for JSON:API payload creation
- `add_relationship()` from `utils/payload.py` for relationship management
- `query_params()` from `utils/request.py` for converting model to API parameters

### API Response Handling
- Handle 204 No Content responses properly, returning `{"status": "success", "status_code": 204}`
- Implement custom redirect handling for pre-signed URLs
- Use proper error handling for JSON parsing failures

### Documentation Standards
- Docstrings with Args/Returns for all functions
- Reference specific code implementations in docs rather than code snippets
- See cost_estimates.py for latest documentation patterns

### Comments Guidelines
Follow KISS principles for comments:
- Only explain the non-obvious "why" behind code choices, not the "what"
- Add comments for complex logic, edge cases, security measures, or API-specific requirements
- Avoid redundant, unnecessary, or self-explanatory comments
- Keep comments concise and directly relevant

## Contributing

For details on how to extend the server, contribute code, and the release process, see our [Contributing Guide](CONTRIBUTING.md).
```

--------------------------------------------------------------------------------
/terraform_cloud_mcp/tools/variables.py:
--------------------------------------------------------------------------------

```python
"""Variable management tools for Terraform Cloud MCP

This module implements workspace variables and variable sets endpoints
of the Terraform Cloud API.

Reference:
- https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspace-variables
- https://developer.hashicorp.com/terraform/cloud-docs/api-docs/variable-sets
"""

from typing import List, Optional

from ..api.client import api_request
from ..utils.decorators import handle_api_errors
from ..utils.payload import create_api_payload
from ..utils.request import query_params
from ..models.base import APIResponse
from ..models.variables import (
    WorkspaceVariableCreateRequest,
    WorkspaceVariableUpdateRequest,
    WorkspaceVariableParams,
    VariableSetCreateRequest,
    VariableSetUpdateRequest,
    VariableSetParams,
    VariableSetVariableParams,
    VariableSetListRequest,
    VariableCategory,
)


# Workspace Variables Tools


@handle_api_errors
async def list_workspace_variables(workspace_id: str) -> APIResponse:
    """List all variables for a workspace.

    Retrieves all variables (both Terraform and environment) configured
    for a specific workspace.

    API endpoint: GET /workspaces/{workspace_id}/vars

    Args:
        workspace_id: The ID of the workspace (format: "ws-xxxxxxxx")

    Returns:
        List of workspace variables with their configuration and values

    See:
        docs/tools/variables.md#list-workspace-variables for reference documentation
    """
    endpoint = f"workspaces/{workspace_id}/vars"
    return await api_request(endpoint, method="GET")


@handle_api_errors
async def create_workspace_variable(
    workspace_id: str,
    key: str,
    category: str,
    params: Optional[WorkspaceVariableParams] = None,
) -> APIResponse:
    """Create a new variable in a workspace.

    Creates a new Terraform or environment variable within a workspace.
    Variables can be marked as sensitive to hide their values.

    API endpoint: POST /workspaces/{workspace_id}/vars

    Args:
        workspace_id: The ID of the workspace (format: "ws-xxxxxxxx")
        key: The variable name/key
        category: Variable category ("terraform" or "env")

        params: Additional variable parameters (optional):
            - value: Variable value
            - description: Description of the variable
            - hcl: Whether the value is HCL code (terraform variables only)
            - sensitive: Whether the variable value is sensitive

    Returns:
        The created variable with its configuration and metadata

    See:
        docs/tools/variables.md#create-workspace-variable for reference documentation
    """
    param_dict = params.model_dump(exclude_none=True) if params else {}
    request = WorkspaceVariableCreateRequest(
        workspace_id=workspace_id,
        key=key,
        category=VariableCategory(category),
        **param_dict,
    )

    payload = create_api_payload(
        resource_type="vars", model=request, exclude_fields={"workspace_id"}
    )

    return await api_request(
        f"workspaces/{workspace_id}/vars", method="POST", data=payload
    )


@handle_api_errors
async def update_workspace_variable(
    workspace_id: str,
    variable_id: str,
    params: Optional[WorkspaceVariableParams] = None,
) -> APIResponse:
    """Update an existing workspace variable.

    Modifies the configuration of an existing workspace variable. Only
    specified attributes will be updated; unspecified attributes remain unchanged.

    API endpoint: PATCH /workspaces/{workspace_id}/vars/{variable_id}

    Args:
        workspace_id: The ID of the workspace (format: "ws-xxxxxxxx")
        variable_id: The ID of the variable (format: "var-xxxxxxxx")

        params: Variable parameters to update (optional):
            - key: New variable name/key
            - value: New variable value
            - description: New description of the variable
            - category: New variable category ("terraform" or "env")
            - hcl: Whether the value is HCL code (terraform variables only)
            - sensitive: Whether the variable value is sensitive

    Returns:
        The updated variable with all current settings and configuration

    See:
        docs/tools/variables.md#update-workspace-variable for reference documentation
    """
    param_dict = params.model_dump(exclude_none=True) if params else {}
    request = WorkspaceVariableUpdateRequest(
        workspace_id=workspace_id, variable_id=variable_id, **param_dict
    )

    payload = create_api_payload(
        resource_type="vars",
        model=request,
        exclude_fields={"workspace_id", "variable_id"},
    )

    return await api_request(
        f"workspaces/{workspace_id}/vars/{variable_id}", method="PATCH", data=payload
    )


@handle_api_errors
async def delete_workspace_variable(workspace_id: str, variable_id: str) -> APIResponse:
    """Delete a workspace variable.

    Permanently removes a variable from a workspace. This action cannot be undone.

    API endpoint: DELETE /workspaces/{workspace_id}/vars/{variable_id}

    Args:
        workspace_id: The ID of the workspace (format: "ws-xxxxxxxx")
        variable_id: The ID of the variable (format: "var-xxxxxxxx")

    Returns:
        Empty response with HTTP 204 status code if successful

    See:
        docs/tools/variables.md#delete-workspace-variable for reference documentation
    """
    endpoint = f"workspaces/{workspace_id}/vars/{variable_id}"
    return await api_request(endpoint, method="DELETE")


# Variable Sets Tools


@handle_api_errors
async def list_variable_sets(
    organization: str,
    page_number: int = 1,
    page_size: int = 20,
) -> APIResponse:
    """List variable sets in an organization.

    Retrieves a paginated list of all variable sets in a Terraform Cloud organization.
    Variable sets allow you to reuse variables across multiple workspaces.

    API endpoint: GET /organizations/{organization}/varsets

    Args:
        organization: The name of the organization
        page_number: The page number to return (default: 1)
        page_size: The number of items per page (default: 20, max: 100)

    Returns:
        Paginated list of variable sets with their configuration and metadata

    See:
        docs/tools/variables.md#list-variable-sets for reference documentation
    """
    request = VariableSetListRequest(
        organization=organization,
        page_number=page_number,
        page_size=page_size,
    )

    params = query_params(request)

    return await api_request(
        f"organizations/{organization}/varsets", method="GET", params=params
    )


@handle_api_errors
async def get_variable_set(varset_id: str) -> APIResponse:
    """Get details for a specific variable set.

    Retrieves comprehensive information about a variable set including its
    variables, workspace assignments, and configuration.

    API endpoint: GET /varsets/{varset_id}

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")

    Returns:
        Variable set details including configuration and relationships

    See:
        docs/tools/variables.md#get-variable-set for reference documentation
    """
    endpoint = f"varsets/{varset_id}"
    return await api_request(endpoint, method="GET")


@handle_api_errors
async def create_variable_set(
    organization: str,
    name: str,
    params: Optional[VariableSetParams] = None,
) -> APIResponse:
    """Create a new variable set in an organization.

    Creates a new variable set which can be used to manage variables across
    multiple workspaces and projects.

    API endpoint: POST /organizations/{organization}/varsets

    Args:
        organization: The name of the organization
        name: The name to give the variable set

        params: Additional variable set parameters (optional):
            - description: Description of the variable set
            - global: Whether this is a global variable set
            - priority: Whether this variable set takes priority over workspace variables

    Returns:
        The created variable set with its configuration and metadata

    See:
        docs/tools/variables.md#create-variable-set for reference documentation
    """
    param_dict = params.model_dump(exclude_none=True) if params else {}
    request = VariableSetCreateRequest(
        organization=organization, name=name, **param_dict
    )

    payload = create_api_payload(
        resource_type="varsets", model=request, exclude_fields={"organization"}
    )

    return await api_request(
        f"organizations/{organization}/varsets", method="POST", data=payload
    )


@handle_api_errors
async def update_variable_set(
    varset_id: str,
    params: Optional[VariableSetParams] = None,
) -> APIResponse:
    """Update an existing variable set.

    Modifies the settings of a variable set. Only specified attributes will be
    updated; unspecified attributes remain unchanged.

    API endpoint: PATCH /varsets/{varset_id}

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")

        params: Variable set parameters to update (optional):
            - name: New name for the variable set
            - description: New description of the variable set
            - global: Whether this is a global variable set
            - priority: Whether this variable set takes priority over workspace variables

    Returns:
        The updated variable set with all current settings and configuration

    See:
        docs/tools/variables.md#update-variable-set for reference documentation
    """
    param_dict = params.model_dump(exclude_none=True) if params else {}
    request = VariableSetUpdateRequest(varset_id=varset_id, **param_dict)

    payload = create_api_payload(
        resource_type="varsets", model=request, exclude_fields={"varset_id"}
    )

    return await api_request(f"varsets/{varset_id}", method="PATCH", data=payload)


@handle_api_errors
async def delete_variable_set(varset_id: str) -> APIResponse:
    """Delete a variable set.

    Permanently removes a variable set and all its variables. This action cannot be undone.
    The variable set will be unassigned from all workspaces and projects.

    API endpoint: DELETE /varsets/{varset_id}

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")

    Returns:
        Empty response with HTTP 204 status code if successful

    See:
        docs/tools/variables.md#delete-variable-set for reference documentation
    """
    endpoint = f"varsets/{varset_id}"
    return await api_request(endpoint, method="DELETE")


@handle_api_errors
async def assign_variable_set_to_workspaces(
    varset_id: str, workspace_ids: List[str]
) -> APIResponse:
    """Assign a variable set to one or more workspaces.

    Makes the variables in a variable set available to the specified workspaces.
    Variables from variable sets take precedence over workspace variables if
    the variable set has priority enabled.

    API endpoint: POST /varsets/{varset_id}/relationships/workspaces

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")
        workspace_ids: List of workspace IDs (format: ["ws-xxxxxxxx", ...])

    Returns:
        Empty response with HTTP 204 status code if successful

    See:
        docs/tools/variables.md#assign-variable-set-to-workspaces for reference documentation
    """
    # Build relationships payload
    relationships_data = []
    for workspace_id in workspace_ids:
        relationships_data.append({"id": workspace_id, "type": "workspaces"})

    payload = {"data": relationships_data}
    endpoint = f"varsets/{varset_id}/relationships/workspaces"
    return await api_request(endpoint, method="POST", data=payload)


@handle_api_errors
async def unassign_variable_set_from_workspaces(
    varset_id: str, workspace_ids: List[str]
) -> APIResponse:
    """Remove a variable set from one or more workspaces.

    Removes the variable set assignment from the specified workspaces. The variables
    will no longer be available in those workspaces.

    API endpoint: DELETE /varsets/{varset_id}/relationships/workspaces

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")
        workspace_ids: List of workspace IDs (format: ["ws-xxxxxxxx", ...])

    Returns:
        Empty response with HTTP 204 status code if successful

    See:
        docs/tools/variables.md#unassign-variable-set-from-workspaces for reference documentation
    """
    # Build relationships payload
    relationships_data = []
    for workspace_id in workspace_ids:
        relationships_data.append({"type": "workspaces", "id": workspace_id})

    payload = {"data": relationships_data}
    endpoint = f"varsets/{varset_id}/relationships/workspaces"
    return await api_request(endpoint, method="DELETE", data=payload)


@handle_api_errors
async def assign_variable_set_to_projects(
    varset_id: str, project_ids: List[str]
) -> APIResponse:
    """Assign a variable set to one or more projects.

    Makes the variables in a variable set available to all workspaces within
    the specified projects.

    API endpoint: POST /varsets/{varset_id}/relationships/projects

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")
        project_ids: List of project IDs (format: ["prj-xxxxxxxx", ...])

    Returns:
        Empty response with HTTP 204 status code if successful

    See:
        docs/tools/variables.md#assign-variable-set-to-projects for reference documentation
    """
    # Build relationships payload
    relationships_data = []
    for project_id in project_ids:
        relationships_data.append({"id": project_id, "type": "projects"})

    payload = {"data": relationships_data}
    endpoint = f"varsets/{varset_id}/relationships/projects"
    return await api_request(endpoint, method="POST", data=payload)


@handle_api_errors
async def unassign_variable_set_from_projects(
    varset_id: str, project_ids: List[str]
) -> APIResponse:
    """Remove a variable set from one or more projects.

    Removes the variable set assignment from the specified projects. The variables
    will no longer be available in workspaces within those projects.

    API endpoint: DELETE /varsets/{varset_id}/relationships/projects

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")
        project_ids: List of project IDs (format: ["prj-xxxxxxxx", ...])

    Returns:
        Empty response with HTTP 204 status code if successful

    See:
        docs/tools/variables.md#unassign-variable-set-from-projects for reference documentation
    """
    # Build relationships payload
    relationships_data = []
    for project_id in project_ids:
        relationships_data.append({"type": "projects", "id": project_id})

    payload = {"data": relationships_data}
    endpoint = f"varsets/{varset_id}/relationships/projects"
    return await api_request(endpoint, method="DELETE", data=payload)


# Variable Set Variables Tools


@handle_api_errors
async def list_variables_in_variable_set(varset_id: str) -> APIResponse:
    """List all variables in a variable set.

    Retrieves all variables that belong to a specific variable set,
    including their configuration and values.

    API endpoint: GET /varsets/{varset_id}/relationships/vars

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")

    Returns:
        List of variables in the variable set with their configuration

    See:
        docs/tools/variables.md#list-variables-in-variable-set for reference documentation
    """
    endpoint = f"varsets/{varset_id}/relationships/vars"
    return await api_request(endpoint, method="GET")


@handle_api_errors
async def create_variable_in_variable_set(
    varset_id: str,
    key: str,
    category: str,
    params: Optional[VariableSetVariableParams] = None,
) -> APIResponse:
    """Create a new variable in a variable set.

    Creates a new Terraform or environment variable within a variable set.
    Variables can be marked as sensitive to hide their values.

    API endpoint: POST /varsets/{varset_id}/relationships/vars

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")
        key: The variable name/key
        category: Variable category ("terraform" or "env")

        params: Additional variable parameters (optional):
            - value: Variable value
            - description: Description of the variable
            - hcl: Whether the value is HCL code (terraform variables only)
            - sensitive: Whether the variable value is sensitive

    Returns:
        The created variable with its configuration and metadata

    See:
        docs/tools/variables.md#create-variable-in-variable-set for reference documentation
    """
    # Create a temporary request-like structure for the variable
    # Note: We don't have specific models for variable set variables yet
    var_data = {
        "key": key,
        "category": VariableCategory(category).value,
    }

    if params:
        param_dict = params.model_dump(exclude_none=True)
        var_data.update(param_dict)

    payload = {"data": {"type": "vars", "attributes": var_data}}

    return await api_request(
        f"varsets/{varset_id}/relationships/vars", method="POST", data=payload
    )


@handle_api_errors
async def update_variable_in_variable_set(
    varset_id: str,
    var_id: str,
    params: Optional[VariableSetVariableParams] = None,
) -> APIResponse:
    """Update an existing variable in a variable set.

    Modifies the configuration of an existing variable within a variable set. Only
    specified attributes will be updated; unspecified attributes remain unchanged.

    API endpoint: PATCH /varsets/{varset_id}/relationships/vars/{var_id}

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")
        var_id: The ID of the variable (format: "var-xxxxxxxx")

        params: Variable parameters to update (optional):
            - key: New variable name/key
            - value: New variable value
            - description: New description of the variable
            - category: New variable category ("terraform" or "env")
            - hcl: Whether the value is HCL code (terraform variables only)
            - sensitive: Whether the variable value is sensitive

    Returns:
        The updated variable with all current settings and configuration

    See:
        docs/tools/variables.md#update-variable-in-variable-set for reference documentation
    """
    param_dict = params.model_dump(exclude_none=True) if params else {}

    payload = {"data": {"type": "vars", "attributes": param_dict}}

    return await api_request(
        f"varsets/{varset_id}/relationships/vars/{var_id}", method="PATCH", data=payload
    )


@handle_api_errors
async def delete_variable_from_variable_set(varset_id: str, var_id: str) -> APIResponse:
    """Delete a variable from a variable set.

    Permanently removes a variable from a variable set. This action cannot be undone.

    API endpoint: DELETE /varsets/{varset_id}/relationships/vars/{var_id}

    Args:
        varset_id: The ID of the variable set (format: "varset-xxxxxxxx")
        var_id: The ID of the variable (format: "var-xxxxxxxx")

    Returns:
        Empty response with HTTP 204 status code if successful

    See:
        docs/tools/variables.md#delete-variable-from-variable-set for reference documentation
    """
    endpoint = f"varsets/{varset_id}/relationships/vars/{var_id}"
    return await api_request(endpoint, method="DELETE")

```
Page 3/3FirstPrevNextLast