Skip to content

Commit da40ce0

Browse files
Merge remote-tracking branch 'origin/main' into fix/nginx-underscore-headers
2 parents 20c870e + f157793 commit da40ce0

File tree

4 files changed

+82
-13
lines changed

4 files changed

+82
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## UNRELEASED
22

3+
FIXES
4+
5+
* Fix sessions handling in stateless and load balanced environments
6+
37
IMPROVEMENTS
48
* Add `Authorization: Bearer` header support for Terraform token in proxy environments
59
* Add `--heartbeat-interval` CLI flag and `MCP_HEARTBEAT_INTERVAL` env var for HTTP heartbeat in load-balanced environments

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,50 @@ To enable stateless mode, set the environment variable:
496496
```bash
497497
export MCP_SESSION_MODE=stateless
498498
```
499+
## Troubleshooting
499500

501+
### Corporate Proxy / TLS Inspection (Zscaler, etc.)
502+
503+
If you're behind a corporate proxy that performs TLS inspection (like Zscaler Internet Access), you may see certificate errors:
504+
```
505+
tls: failed to verify certificate: x509: certificate signed by unknown authority
506+
```
507+
508+
**Solution: Mount your corporate CA certificate into the container:**
509+
```bash
510+
docker run -i --rm \
511+
-v /path/to/corporate-ca.pem:/etc/ssl/certs/corporate-ca.pem \
512+
-e SSL_CERT_FILE=/etc/ssl/certs/corporate-ca.pem \
513+
hashicorp/terraform-mcp-server:0.4.0
514+
```
515+
516+
For MCP client configurations:
517+
```json
518+
{
519+
"mcpServers": {
520+
"terraform": {
521+
"command": "docker",
522+
"args": [
523+
"run",
524+
"-i",
525+
"--rm",
526+
"-v", "/path/to/corporate-ca.pem:/etc/ssl/certs/corporate-ca.pem",
527+
"-e", "SSL_CERT_FILE=/etc/ssl/certs/corporate-ca.pem",
528+
"-e", "TFE_TOKEN=<>",
529+
"hashicorp/terraform-mcp-server:0.4.0"
530+
]
531+
}
532+
}
533+
}
534+
```
535+
536+
**Alternative: Run the binary directly**
537+
538+
If Docker is not permitted in your environment, you can install and run the server binary directly, which will use your system's certificate store:
539+
```bash
540+
go install github.com/hashicorp/terraform-mcp-server/cmd/terraform-mcp-server@latest
541+
terraform-mcp-server stdio
542+
```
500543
## Development
501544

502545
### Prerequisites

cmd/terraform-mcp-server/main.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/hashicorp/terraform-mcp-server/pkg/toolsets"
1919
"github.com/hashicorp/terraform-mcp-server/version"
2020

21+
"github.com/mark3labs/mcp-go/mcp"
2122
"github.com/mark3labs/mcp-go/server"
2223
log "github.com/sirupsen/logrus"
2324
"github.com/spf13/cobra"
@@ -69,6 +70,23 @@ func NewServer(version string, logger *log.Logger, enabledToolsets []string, opt
6970
hooks.AddOnUnregisterSession(func(ctx context.Context, session server.ClientSession) {
7071
client.EndSessionHandler(ctx, session, logger)
7172
})
73+
// When running multiple sessions of the MCP server (load balancing), calling client.NewSessionHandler
74+
// in both BeforeListTools and BeforeCallTool ensures that a session that was not initialized during
75+
// registration (e.g., due to being routed to a different instance) will still have its clients created
76+
// before any tool calls are made. This provides a safety net to ensure that all sessions have
77+
// the necessary clients initialized regardless of how they are routed.
78+
hooks.AddBeforeListTools(func(ctx context.Context, id any, message *mcp.ListToolsRequest) {
79+
session := server.ClientSessionFromContext(ctx)
80+
if session != nil {
81+
client.NewSessionHandler(ctx, session, logger)
82+
}
83+
})
84+
hooks.AddBeforeCallTool(func(ctx context.Context, id any, message *mcp.CallToolRequest) {
85+
session := server.ClientSessionFromContext(ctx)
86+
if session != nil {
87+
client.NewSessionHandler(ctx, session, logger)
88+
}
89+
})
7290

7391
// Add hooks to options
7492
opts = append(opts, server.WithHooks(hooks))
@@ -210,19 +228,6 @@ func shouldUseStreamableHTTPMode() bool {
210228
os.Getenv("MCP_ENDPOINT") != ""
211229
}
212230

213-
// shouldUseStatelessMode returns true if the MCP_SESSION_MODE environment variable is set to "stateless"
214-
func shouldUseStatelessMode() bool {
215-
mode := strings.ToLower(os.Getenv("MCP_SESSION_MODE"))
216-
217-
// Explicitly check for "stateless" value
218-
if mode == "stateless" {
219-
return true
220-
}
221-
222-
// All other values (including empty string, "stateful", or any other value) default to stateful mode
223-
return false
224-
}
225-
226231
// getHTTPPort returns the port from environment variables or default
227232
func getHTTPPort() string {
228233
if port := os.Getenv("TRANSPORT_PORT"); port != "" {
@@ -239,6 +244,19 @@ func getHTTPHost() string {
239244
return "127.0.0.1"
240245
}
241246

247+
// shouldUseStatelessMode returns true if the MCP_SESSION_MODE environment variable is set to "stateless"
248+
func shouldUseStatelessMode() bool {
249+
mode := strings.ToLower(os.Getenv("MCP_SESSION_MODE"))
250+
251+
// Explicitly check for "stateless" value
252+
if mode == "stateless" {
253+
return true
254+
}
255+
256+
// All other values (including empty string, "stateful", or any other value) default to stateful mode
257+
return false
258+
}
259+
242260
// Add function to get endpoint path from environment or flag
243261
func getEndpointPath(cmd *cobra.Command) string {
244262
// First check environment variable

pkg/client/session.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ type contextKey string
1515

1616
// NewSessionHandler initializes clients for the session
1717
func NewSessionHandler(ctx context.Context, session server.ClientSession, logger *log.Logger) {
18+
if _, ok := activeTfeClients.Load(session.SessionID()); ok {
19+
return
20+
}
21+
1822
// Create both TFE and HTTP clients for the session
1923
tfeClient, err := CreateTfeClientForSession(ctx, session, logger)
2024
if err != nil {

0 commit comments

Comments
 (0)