Skip to content

--tools and --toolsets flags silently ignored in streamable-HTTP env-var mode #376

@sebin

Description

@sebin

Summary

--tools and --toolsets CLI flags are silently ignored when the server is started in streamable-HTTP mode via the TRANSPORT_MODE env var. The flags always fall back to their declared defaults (--toolsets="all", --tools=""), regardless of what is passed on the command line. The flags work as expected with the explicit terraform-mcp-server streamable-http … subcommand invocation — only the env-var-driven shortcut is affected.

This is a regression for anyone running the server in containers/Kubernetes/Bedrock AgentCore, where the convention is to set transport via env vars and tool filtering via flags. The server then exposes every tool (including destructive HCP Terraform writes) when only registry/read tools were requested.

Reproduction

Tested against hashicorp/terraform-mcp-server:0.5.2 (also present on main, commit 5ca056e):

docker run -d --rm \
  -e TRANSPORT_MODE=streamable-http \
  -e TRANSPORT_HOST=0.0.0.0 \
  -e TRANSPORT_PORT=8080 \
  -e MCP_SESSION_MODE=stateless \
  -e TFE_TOKEN=dummy \
  -p 18080:8080 \
  hashicorp/terraform-mcp-server:0.5.2 \
  --tools=list_workspaces,get_workspace_details

# Verify the binary received --tools correctly:
docker exec <container> ps -ef
#   PID   USER     COMMAND
#     1   root     terraform-mcp-server streamable-http --tools=list_workspaces,get_workspace_details

# tools/list returns the full 43-tool catalog instead of the requested 2:
curl -s -X POST http://localhost:18080/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  | grep -oE '"name":"[a-zA-Z_]+"' | sort -u | wc -l
# → 43 (including attach_policy_set_to_workspaces, create_workspace, create_run, …)

# Startup log:
#   level=info msg="Starting in Streamable HTTP mode based on environment configuration"
#   level=info msg="Enabled toolsets: [all]"
# (no "Enabled individual tools" line — confirming the flag was never parsed)

The same invocation without TRANSPORT_MODE (taking the cobra subcommand path) honours --tools correctly.

Root cause

cmd/terraform-mcp-server/main.go (current main, lines 286–301):

if shouldUseStreamableHTTPMode() {
    logger.Info("Starting in Streamable HTTP mode based on environment configuration")
    …
    enabledToolsets := getToolsetsFromCmd(rootCmd, logger)
    …
    if err := runHTTPServer(…); err != nil { … }
    return
}

// Fall back to normal CLI behavior
if err := rootCmd.Execute(); err != nil { … }

When shouldUseStreamableHTTPMode() returns true (because TRANSPORT_MODE/TRANSPORT_HOST/TRANSPORT_PORT/MCP_ENDPOINT is set), the env-var-driven fast path runs before rootCmd.Execute() is called. cobra's flag parser is therefore never invoked, so:

  • rootCmd.PersistentFlags().GetString("toolsets") returns the declared default "all".
  • rootCmd.PersistentFlags().GetString("tools") returns "".

getToolsetsFromCmd(rootCmd, logger) then takes the --toolsets="all" branch unconditionally and parseIndividualTools is never reached.

The explicit streamable-http subcommand path works because cobra's Execute() parses flags before calling the subcommand's Run.

Suggested fix

Move the env-var-driven HTTP-mode detection inside runDefaultCommand (the default Run of rootCmd), so cobra's Execute() always runs and parses persistent flags first. main() collapses to:

func main() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

…and the streamable-HTTP-via-env branch moves to runDefaultCommand, after getToolsetsFromCmd(cmd, logger). Happy to send a PR.

Environment

  • terraform-mcp-server v0.5.2 (also reproducible against main at 5ca056e)
  • TRANSPORT_MODE=streamable-http
  • Both hashicorp/terraform-mcp-server:0.5.2 Docker image and binaries built from source

Metadata

Metadata

Assignees

No one assigned

    Labels

    TriagedIssues has been triaged and captured by the TF MCP team

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions