Skip to content
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

FEATURES

* Adding tools for working with workspaces in HCP Terraform and TFE.
* Authentication for HCP Terraform & TFE and restructure the repo. See [#121](https://github.com/hashicorp/terraform-mcp-server/pull/121) See [#145](https://github.com/hashicorp/terraform-mcp-server/pull/145)
* Implement dynamic tool registration. See [#121](https://github.com/hashicorp/terraform-mcp-server/pull/121)
* Adding 2 new HCP TF/TFE tools for admins. List Terraform organizations & projects. See [#121](https://github.com/hashicorp/terraform-mcp-server/pull/121)
Expand Down
99 changes: 99 additions & 0 deletions Tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Running tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these the instructions to run e2e tests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is to test with actual HCP Terraform tokens, I haven't added live testing yet (only in/out mock tests), but we should start adding live HCP TF tests to make sure tools are running they way they're supposed to

it should be similar to tfe-go

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local testing for now but we should add them to GitHub Actions


The HCP Terraform and TFE tools for the `terraform-mcp-server` relies on acceptance tests against their respective APIs. terraform-mcp-server is tested against HCP Terraform by our CI environment, and against Terraform Enterprise prior to release or otherwise as needed.

## 1. (Optional) Create repositories for policy sets and registry modules

If you are planning to run the full suite of tests or work on policy sets or registry modules, you'll need to set up repositories for them in GitHub.

Your policy set repository will need the following:
1. A policy set stored in a subdirectory `policy-sets/foo`
1. A branch other than `main` named `policies`

Alternatively, you can start with this [example repository for policy sets](https://github.com/hashicorp/test-policy-set) by forking the repository to your GitHub account, then setting `GITHUB_POLICY_SET_IDENTIFIER` to the forked repository identifier `your-github-handle/test-policy-set`.

Your registry module repository will need to be a [valid module](https://developer.hashicorp.com/terraform/cloud-docs/registry/publish-modules#preparing-a-module-repository).
It will need the following:
1. To be named `terraform-<PROVIDER>-<NAME>`
1. At least one valid SemVer tag in the format `x.y.z`
[terraform-random-module](https://github.com/caseylang/terraform-random-module) is a good example repo.

## 2. Set up environment variables (ENVVARS)

You'll need to have environment variables setup in your environment to run the tests. There are different options to facilitate setting up environment variables, using the tool [envchain](https://github.com/sorah/envchain) is one option:
1. Install envchain - [refer to the envchain README for details](https://github.com/sorah/envchain#installation)
2. Pick a namespace for storing your environment variables, such as: `terraform-mcp-server`. Then, for each environment variable you need to set, run the following command:
```sh
envchain --set YOUR_NAMESPACE_HERE ENVIRONMENT_VARIABLE_HERE
```
**OR**

Set all of the environment variables at once with the following command:
```sh
envchain --set YOUR_NAMESPACE_HERE TFE_ADDRESS TFE_TOKEN OAUTH_CLIENT_GITHUB_TOKEN GITHUB_POLICY_SET_IDENTIFIER
```

### Required ENVVARS

Tests are run against an actual backend so they require a valid backend address and token:

1. `TFE_ADDRESS` - URL of a HCP Terraform or Terraform Enterprise instance to be used for testing, including scheme. Example: `https://tfe.local`
1. `TFE_TOKEN` - A [user API token](https://developer.hashicorp.com/terraform/cloud-docs/users-teams-organizations/users#tokens) for the HCP Terraform or Terraform Enterprise instance being used for testing.

**Note:** Alternatively, you can set `TFE_HOSTNAME` which serves as a fallback for `TFE_ADDRESS`. It will only be used if `TFE_ADDRESS` is not set and will resolve the host to an `https` scheme. Example: `tfe.local` => resolves to `https://tfe.local`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any occurrence of TFE_HOSTNAME is this repo, where is this fallback handled?

Copy link
Member Author

@gautambaghel gautambaghel Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tfe-go automatically picks it up I believe


## 3. Make sure run queue settings are correct

In order for the tests relating to queuing and capacity to pass, FRQ (fair run queuing) should be
enabled with a limit of 2 concurrent runs per organization on the HCP Terraform or Terraform Enterprise instance you are using for testing.

## 4. Run tests

For most situations, it's recommended to run specific tests because it takes about 20 minutes to run all of the tests.

### Running specific tests

Typically, you'll want to run specific tests. The commands below use notification configurations as an example.

#### With envchain:
```sh
$ envchain YOUR_NAMESPACE_HERE go test -run TestNotificationConfiguration -v ./...
```

#### Without envchain (Using TFE_ADDRESS):
```sh
$ TFE_TOKEN=xyz TFE_ADDRESS=https://tfe.local ENABLE_TFE=1 go test -run TestNotificationConfiguration -v ./...
```

#### Without envchain (Using TFE_HOSTNAME):
```sh
$ TFE_TOKEN=xyz TFE_HOSTNAME=tfe.local ENABLE_TFE=1 go test -run TestNotificationConfiguration -v ./...
```

#### Using Makefile target `test`
```sh
TFE_TOKEN=xyz TFE_ADDRESS=https://tfe.local TESTARGS="-run TestNotificationConfiguration" make test
```

### Running all tests
It takes about 20 minutes to run all of the tests, so specify a larger timeout when you run the tests (_the default timeout is 10 minutes_):

#### With envchain:
```sh
$ envchain YOUR_NAMESPACE_HERE go test ./... -timeout=30m
```

#### Without envchain (Using TFE_ADDRESS):
```sh
$ TFE_TOKEN=xyz TFE_ADDRESS=https://tfe.local ENABLE_TFE=1 go test ./... -timeout=30m
```

#### Without envchain (Using TFE_HOSTNAME):
```sh
$ TFE_TOKEN=xyz TFE_HOSTNAME=tfe.local ENABLE_TFE=1 go test ./... -timeout=30m
```


### Running tests for HCP Terraform features that require paid plans (HashiCorp Employees)

You can use the test helper `newSubscriptionUpdater()` to upgrade your test organization to a Business Plan, giving the organization access to all features in HCP Terraform. This method requires `TFE_TOKEN` to be a user token with administrator access in the target test environment. Furthermore, you **can not** have enterprise features enabled (`ENABLE_TFE=1`) in order to use this method since the API call fails against Terraform Enterprise test environments.
15 changes: 4 additions & 11 deletions cmd/terraform-mcp-server/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,6 @@ func serverInit(ctx context.Context, hcServer *server.MCPServer, logger *log.Log
}

func streamableHTTPServerInit(ctx context.Context, hcServer *server.MCPServer, logger *log.Logger, host string, port string, endpointPath string) error {
// Check if stateless mode is enabled
isStateless := shouldUseStatelessMode()

// Ensure endpoint path starts with /
endpointPath = path.Join("/", endpointPath)
// Create StreamableHTTP server which implements the new streamable-http transport
Expand All @@ -191,14 +188,10 @@ func streamableHTTPServerInit(ctx context.Context, hcServer *server.MCPServer, l
// Log the endpoint path being used
logger.Infof("Using endpoint path: %s", endpointPath)

// Only add the WithStateLess option if stateless mode is enabled
// TODO: fix this in mcp-go ver 0.33.0 or higher
if isStateless {
opts = append(opts, server.WithStateLess(true))
logger.Infof("Running in stateless mode")
} else {
logger.Infof("Running in stateful mode (default)")
}
// Check if stateless mode is enabled
isStateless := shouldUseStatelessMode()
opts = append(opts, server.WithStateLess(isStateless))
logger.Infof("Running with stateless mode: %v", isStateless)

baseStreamableServer := server.NewStreamableHTTPServer(hcServer, opts...)

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-retryablehttp v0.7.8
github.com/hashicorp/go-tfe v1.91.1
github.com/hashicorp/jsonapi v1.5.0
github.com/mark3labs/mcp-go v0.39.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.10.1
Expand All @@ -24,7 +25,6 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-slug v0.16.7 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/jsonapi v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
Expand Down
14 changes: 13 additions & 1 deletion pkg/client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

package client

import "time"
import (
"time"

"github.com/hashicorp/go-tfe"
)

type ProviderDetail struct {
ProviderName string
Expand Down Expand Up @@ -434,3 +438,11 @@ type TerraformPolicyDetails struct {
} `json:"links"`
} `json:"included"`
}

type WorkspaceToolResponse struct {
Type string `jsonapi:"primary,tool"`
Success bool `jsonapi:"attr,success"`
Workspace *tfe.Workspace `jsonapi:"attr,workspace,omitempty"`
Variables []*tfe.Variable `jsonapi:"polyrelation,variables,omitempty"`
Readme string `jsonapi:"attr,readme,omitempty"`
}
16 changes: 16 additions & 0 deletions pkg/tools/dynamic_tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,22 @@ func (r *DynamicToolRegistry) registerTFETools() {
listTerraformProjectsTool := r.createDynamicTFETool("list_terraform_projects", tfeTools.ListTerraformProjects)
r.mcpServer.AddTool(listTerraformProjectsTool.Tool, listTerraformProjectsTool.Handler)

// Workspace management tools
ListWorkspacesTool := r.createDynamicTFETool("list_workspaces", tfeTools.ListWorkspaces)
r.mcpServer.AddTool(ListWorkspacesTool.Tool, ListWorkspacesTool.Handler)

getWorkspaceDetailsTool := r.createDynamicTFETool("get_workspace_details", tfeTools.GetWorkspaceDetails)
r.mcpServer.AddTool(getWorkspaceDetailsTool.Tool, getWorkspaceDetailsTool.Handler)

createWorkspaceTool := r.createDynamicTFETool("create_workspace", tfeTools.CreateWorkspace)
r.mcpServer.AddTool(createWorkspaceTool.Tool, createWorkspaceTool.Handler)

updateWorkspaceTool := r.createDynamicTFETool("update_workspace", tfeTools.UpdateWorkspace)
r.mcpServer.AddTool(updateWorkspaceTool.Tool, updateWorkspaceTool.Handler)

deleteWorkspaceSafelyTool := r.createDynamicTFETool("delete_workspace_safely", tfeTools.DeleteWorkspaceSafely)
r.mcpServer.AddTool(deleteWorkspaceSafelyTool.Tool, deleteWorkspaceSafelyTool.Handler)

// Private provider tools
searchPrivateProvidersTool := r.createDynamicTFETool("search_private_providers", tfeTools.SearchPrivateProviders)
r.mcpServer.AddTool(searchPrivateProvidersTool.Tool, searchPrivateProvidersTool.Handler)
Expand Down
Loading
Loading