Skip to content

Commit 7efd187

Browse files
Fixing port & host name, should fix #101, Fixing/chaning resources implementation (#102)
* fixing port & host name, should fix #101, fixing resources --------- Co-authored-by: Copilot <[email protected]>
1 parent 81c2587 commit 7efd187

File tree

5 files changed

+109
-71
lines changed

5 files changed

+109
-71
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ Modern HTTP-based transport supporting both direct HTTP requests and Server-Sent
3232
**Features:**
3333
- **Endpoint**: `http://{hostname}:8080/mcp`
3434
- **Health Check**: `http://{hostname}:8080/health`
35-
- **Environment Configuration**: Set `MODE=http` or `PORT=8080` to enable
35+
- **Environment Configuration**: Set `TRANSPORT_MODE=http` or `TRANSPORT_PORT=8080` to enable
3636

3737
**Environment Variables:**
3838

3939
| Variable | Description | Default |
4040
|----------|-------------|---------|
41-
| `MODE` | Set to `http` to enable HTTP transport | `stdio` |
42-
| `PORT` | HTTP server port | `8080` |
41+
| `TRANSPORT_MODE` | Set to `http` to enable HTTP transport | `stdio` |
42+
| `TRANSPORT_HOST` | Host to bind the HTTP server | `0.0.0.0` |
43+
| `TRANSPORT_PORT` | HTTP server port | `8080` |
4344

4445
## Command Line Options
4546

@@ -48,7 +49,7 @@ Modern HTTP-based transport supporting both direct HTTP requests and Server-Sent
4849
terraform-mcp-server stdio [--log-file /path/to/log]
4950

5051
# HTTP mode
51-
terraform-mcp-server http [--port 8080] [--host 0.0.0.0] [--log-file /path/to/log]
52+
terraform-mcp-server http [--transport-port 8080] [--transport-host 0.0.0.0] [--log-file /path/to/log]
5253
```
5354

5455
## Installation

cmd/terraform-mcp-server/init.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
stdlog "log"
1111
"net/http"
1212
"os"
13+
1314
"github.com/hashicorp/terraform-mcp-server/pkg/hashicorp/tfregistry"
1415

1516
"github.com/mark3labs/mcp-go/server"
@@ -31,11 +32,11 @@ func init() {
3132
cobra.OnInitialize(initConfig)
3233
rootCmd.SetVersionTemplate("{{.Short}}\n{{.Version}}\n")
3334
rootCmd.PersistentFlags().String("log-file", "", "Path to log file")
34-
35+
3536
// Add HTTP command flags (avoid 'h' shorthand conflict with help)
36-
httpCmd.Flags().StringP("port", "p", "8080", "Port to listen on")
37-
httpCmd.Flags().String("host", "0.0.0.0", "Host to bind to")
38-
37+
httpCmd.Flags().String("transport-host", "0.0.0.0", "Host to bind to")
38+
httpCmd.Flags().StringP("transport-port", "p", "8080", "Port to listen on")
39+
3940
rootCmd.AddCommand(stdioCmd)
4041
rootCmd.AddCommand(httpCmd)
4142
}
@@ -80,7 +81,7 @@ func serverInit(ctx context.Context, hcServer *server.MCPServer, logger *log.Log
8081
errC <- stdioServer.Listen(ctx, in, out)
8182
}()
8283

83-
_, _ = fmt.Fprintf(os.Stderr, "HCP Terraform MCP Server running on stdio\n")
84+
_, _ = fmt.Fprintf(os.Stderr, "Terraform MCP Server running on stdio\n")
8485

8586
// Wait for shutdown signal
8687
select {

cmd/terraform-mcp-server/main.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,23 @@ var (
6363
stdlog.Fatal("Failed to initialize logger:", err)
6464
}
6565

66-
port, err := cmd.Flags().GetString("port")
66+
port, err := cmd.Flags().GetString("transport-port")
6767
if err != nil {
68-
stdlog.Fatal("Failed to get port:", err)
68+
stdlog.Fatal("Failed to get streamableHTTP port:", err)
6969
}
70-
host, err := cmd.Flags().GetString("host")
70+
host, err := cmd.Flags().GetString("transport-host")
7171
if err != nil {
72-
stdlog.Fatal("Failed to get host:", err)
72+
stdlog.Fatal("Failed to get streamableHTTP host:", err)
7373
}
7474

7575
if err := runHTTPServer(logger, host, port); err != nil {
76-
stdlog.Fatal("failed to run HTTP server:", err)
76+
stdlog.Fatal("failed to run streamableHTTP server:", err)
7777
}
7878
},
7979
}
8080
)
8181

82-
func runHTTPServer(logger *log.Logger, host, port string) error {
82+
func runHTTPServer(logger *log.Logger, host string, port string) error {
8383
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
8484
defer stop()
8585

@@ -89,7 +89,7 @@ func runHTTPServer(logger *log.Logger, host, port string) error {
8989
return httpServerInit(ctx, hcServer, logger, host, port)
9090
}
9191

92-
func httpServerInit(ctx context.Context, hcServer *server.MCPServer, logger *log.Logger, host, port string) error {
92+
func httpServerInit(ctx context.Context, hcServer *server.MCPServer, logger *log.Logger, host string, port string) error {
9393
// Create StreamableHTTP server which implements the new streamable-http transport
9494
// This is the modern MCP transport that supports both direct HTTP responses and SSE streams
9595
streamableServer := server.NewStreamableHTTPServer(hcServer,
@@ -214,12 +214,12 @@ func main() {
214214

215215
// shouldUseHTTPMode checks if environment variables indicate HTTP mode
216216
func shouldUseHTTPMode() bool {
217-
return os.Getenv("MODE") == "http" || os.Getenv("PORT") != ""
217+
return os.Getenv("TRANSPORT_MODE") == "http" || os.Getenv("TRANSPORT_PORT") != ""
218218
}
219219

220220
// getHTTPPort returns the port from environment variables or default
221221
func getHTTPPort() string {
222-
if port := os.Getenv("PORT"); port != "" {
222+
if port := os.Getenv("TRANSPORT_PORT"); port != "" {
223223
return port
224224
}
225225
return "8080"

e2e/e2e_test.go

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"os"
1212
"os/exec"
1313
"strings"
14-
"sync"
1514
"testing"
1615
"time"
1716

@@ -20,26 +19,22 @@ import (
2019
"github.com/stretchr/testify/require"
2120
)
2221

23-
var initOnce sync.Once
24-
var globalClient mcpClient.MCPClient
25-
var globalCleanup func()
26-
2722
func TestE2E(t *testing.T) {
2823
buildDockerImage(t)
29-
24+
3025
// Ensure all test containers are cleaned up at the end
3126
t.Cleanup(func() {
3227
cleanupAllTestContainers(t)
3328
})
34-
29+
3530
testCases := []struct {
36-
name string
31+
name string
3732
clientFactory func(t *testing.T) (mcpClient.MCPClient, func())
3833
}{
3934
{"Stdio", createStdioClient},
4035
{"HTTP", createHTTPClient},
4136
}
42-
37+
4338
for _, tc := range testCases {
4439
t.Run(tc.name, func(t *testing.T) {
4540
client, cleanup := tc.clientFactory(t)
@@ -316,54 +311,54 @@ func createStdioClient(t *testing.T) (mcpClient.MCPClient, func()) {
316311
t.Log("Starting Stdio MCP client...")
317312
client, err := mcpClient.NewStdioMCPClient(args[0], []string{}, args[1:]...)
318313
require.NoError(t, err, "expected to create stdio client successfully")
319-
314+
320315
cleanup := func() {
321316
client.Close()
322317
}
323-
318+
324319
return client, cleanup
325320
}
326321

327322
// createHTTPClient creates an HTTP-based MCP client
328323
func createHTTPClient(t *testing.T) (mcpClient.MCPClient, func()) {
329324
t.Log("Starting HTTP MCP server...")
330-
325+
331326
port := getTestPort()
332327
baseURL := fmt.Sprintf("http://localhost:%s", port)
333328
mcpURL := fmt.Sprintf("http://localhost:%s/mcp", port)
334329

335330
// Start container in HTTP mode
336331
containerID := startHTTPContainer(t, port)
337-
332+
338333
// Ensure container cleanup even if test fails
339334
t.Cleanup(func() {
340335
stopContainer(t, containerID)
341336
})
342-
337+
343338
// Wait for server to be ready
344339
waitForServer(t, baseURL)
345-
340+
346341
// Create client with MCP endpoint
347342
client, err := mcpClient.NewStreamableHttpClient(mcpURL)
348343
require.NoError(t, err, "expected to create HTTP client successfully")
349-
344+
350345
cleanup := func() {
351346
if client != nil {
352347
client.Close()
353348
}
354349
// Container cleanup handled by t.Cleanup()
355350
}
356-
351+
357352
return client, cleanup
358353
}
359354

360355
// startHTTPContainer starts a Docker container in HTTP mode and returns container ID
361356
func startHTTPContainer(t *testing.T, port string) string {
362357
portMapping := fmt.Sprintf("%s:8080", port)
363-
cmd := exec.Command("docker", "run", "-d", "--rm", "-e", "MODE=http", "-p", portMapping, "terraform-mcp-server:test-e2e")
358+
cmd := exec.Command("docker", "run", "-d", "--rm", "-e", "TRANSPORT_MODE=http", "-p", portMapping, "terraform-mcp-server:test-e2e")
364359
output, err := cmd.Output()
365360
require.NoError(t, err, "expected to start HTTP container successfully")
366-
361+
367362
containerID := string(output)[:12] // First 12 chars of container ID
368363
t.Logf("Started HTTP container: %s on port %s", containerID, port)
369364
return containerID
@@ -392,7 +387,7 @@ func stopContainer(t *testing.T, containerID string) {
392387
if containerID == "" {
393388
return
394389
}
395-
390+
396391
t.Logf("Stopping container: %s", containerID)
397392
cmd := exec.Command("docker", "stop", containerID)
398393
if err := cmd.Run(); err != nil {
@@ -410,21 +405,21 @@ func stopContainer(t *testing.T, containerID string) {
410405
// cleanupAllTestContainers stops all containers created by this test
411406
func cleanupAllTestContainers(t *testing.T) {
412407
t.Log("Cleaning up all test containers...")
413-
408+
414409
// Find all containers with our test image
415410
cmd := exec.Command("docker", "ps", "-q", "--filter", "ancestor=terraform-mcp-server:test-e2e")
416411
output, err := cmd.Output()
417412
if err != nil {
418413
t.Logf("Warning: failed to list test containers: %v", err)
419414
return
420415
}
421-
416+
422417
containerIDs := string(output)
423418
if containerIDs == "" {
424419
t.Log("No test containers found to cleanup")
425420
return
426421
}
427-
422+
428423
// Stop all found containers
429424
stopCmd := exec.Command("docker", "stop")
430425
stopCmd.Stdin = strings.NewReader(containerIDs)
Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,104 @@
1-
// Copyright (c) HashiCorp, Inc.
2-
// SPDX-License-Identifier: MPL-2.0
3-
41
package tfregistry
52

63
import (
74
"context"
85
"fmt"
6+
"io"
97
"net/http"
108

119
"github.com/mark3labs/mcp-go/mcp"
1210
"github.com/mark3labs/mcp-go/server"
1311
log "github.com/sirupsen/logrus"
1412
)
1513

14+
// Base URL for the Terraform style guide and module development guide markdown files
15+
const terraformGuideRawURL = "https://raw.githubusercontent.com/hashicorp/web-unified-docs/main/content/terraform/v1.12.x/docs/language"
16+
17+
// RegisterResources adds the new resource
1618
func RegisterResources(hcServer *server.MCPServer, registryClient *http.Client, logger *log.Logger) {
17-
// Add resources for official and partner providers
18-
hcServer.AddResource(ProviderResource(registryClient, fmt.Sprintf("%sproviders/official", PROVIDER_BASE_PATH), "Official Providers list", "official", logger))
19-
hcServer.AddResource(ProviderResource(registryClient, fmt.Sprintf("%sproviders/partner", PROVIDER_BASE_PATH), "Partner Providers list", "partner", logger))
19+
hcServer.AddResource(TerraformStyleGuideResource(registryClient, logger))
20+
hcServer.AddResource(TerraformModuleDevGuideResource(registryClient, logger))
2021
}
2122

22-
func ProviderResource(registryClient *http.Client, resourceURI string, description string, providerType string, logger *log.Logger) (mcp.Resource, server.ResourceHandlerFunc) {
23+
// TerraformStyleGuideResource returns the resource and handler for the style guide
24+
func TerraformStyleGuideResource(httpClient *http.Client, logger *log.Logger) (mcp.Resource, server.ResourceHandlerFunc) {
25+
resourceURI := "/terraform/style-guide"
26+
description := "Terraform Style Guide"
27+
2328
return mcp.NewResource(
2429
resourceURI,
2530
description,
2631
mcp.WithMIMEType("text/markdown"),
2732
mcp.WithResourceDescription(description),
28-
// TODO: Add pagination parameters here using the correct mcp-go mechanism
29-
// Example (conceptual):
30-
// mcp.WithInteger("page_number", mcp.Description("Page number"), mcp.Optional()),
31-
// mcp.WithInteger("page_size", mcp.Description("Page size"), mcp.Optional()),
3233
),
3334
func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
34-
listOfProviders, err := GetProviderList(registryClient, providerType, logger)
35+
resp, err := httpClient.Get(fmt.Sprintf("%s/style.mdx", terraformGuideRawURL))
36+
if err != nil {
37+
return nil, logAndReturnError(logger, "Error fetching Terraform Style Guide markdown", err)
38+
}
39+
defer resp.Body.Close()
40+
if resp.StatusCode != http.StatusOK {
41+
return nil, logAndReturnError(logger, "Non-200 response fetching Terraform Style Guide markdown", fmt.Errorf("status: %s", resp.Status))
42+
}
43+
body, err := io.ReadAll(resp.Body)
3544
if err != nil {
36-
return nil, logAndReturnError(logger, fmt.Sprintf("Provider Resource: error getting %s provider list", providerType), err)
45+
return nil, logAndReturnError(logger, "Error reading Terraform Style Guide markdown", err)
3746
}
38-
resourceContents := make([]mcp.ResourceContents, len(listOfProviders))
39-
for i, provider := range listOfProviders {
40-
namespace, name, version := ExtractProviderNameAndVersion(fmt.Sprintf("%s/%s/name/%s/version/latest", PROVIDER_BASE_PATH, provider["namespace"], provider["name"]))
41-
logger.Debugf("Extracted namespace: %s, name: %s, version: %s", namespace, name, version)
47+
return []mcp.ResourceContents{
48+
mcp.TextResourceContents{
49+
MIMEType: "text/markdown",
50+
URI: resourceURI,
51+
Text: string(body),
52+
},
53+
}, nil
54+
}
55+
}
4256

43-
versionNumber, err := GetLatestProviderVersion(registryClient, namespace, name, logger)
44-
if err != nil {
45-
return nil, logAndReturnError(logger, fmt.Sprintf("Provider Resource: error getting %s/%s provider version %s", namespace, name, versionNumber), err)
46-
}
57+
// TerraformModuleDevGuideResource returns a resource and handler for the Terraform Module Development Guide markdown files
58+
func TerraformModuleDevGuideResource(httpClient *http.Client, logger *log.Logger) (mcp.Resource, server.ResourceHandlerFunc) {
59+
resourceURI := "/terraform/module-development"
60+
description := "Terraform Module Development Guide"
4761

48-
providerVersionUri := fmt.Sprintf("%s/%s/name/%s/version/%s", PROVIDER_BASE_PATH, namespace, name, versionNumber)
49-
logger.Debugf("Provider resource - providerVersionUri: %s", providerVersionUri)
62+
var urls = []struct {
63+
Name string
64+
URL string
65+
}{
66+
{"index", fmt.Sprintf("%s/modules/develop/index.mdx", terraformGuideRawURL)},
67+
{"composition", fmt.Sprintf("%s/modules/develop/composition.mdx", terraformGuideRawURL)},
68+
{"structure", fmt.Sprintf("%s/modules/develop/structure.mdx", terraformGuideRawURL)},
69+
{"providers", fmt.Sprintf("%s/modules/develop/providers.mdx", terraformGuideRawURL)},
70+
{"publish", fmt.Sprintf("%s/modules/develop/publish.mdx", terraformGuideRawURL)},
71+
{"refactoring", fmt.Sprintf("%s/modules/develop/refactoring.mdx", terraformGuideRawURL)},
72+
}
5073

51-
providerDocs, err := ProviderResourceTemplateHandler(registryClient, providerVersionUri, logger)
74+
return mcp.NewResource(
75+
resourceURI,
76+
description,
77+
mcp.WithMIMEType("text/markdown"),
78+
mcp.WithResourceDescription(description),
79+
),
80+
func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
81+
var contents []mcp.ResourceContents
82+
for _, u := range urls {
83+
resp, err := httpClient.Get(u.URL)
5284
if err != nil {
53-
return nil, logAndReturnError(logger, fmt.Sprintf("Provider Resource: error with provider template handler %s/%s provider version %s details", namespace, name, versionNumber), err)
85+
return nil, logAndReturnError(logger, fmt.Sprintf("Error fetching %s markdown", u.Name), err)
5486
}
55-
resourceContents[i] = mcp.TextResourceContents{
56-
MIMEType: "text/markdown",
57-
URI: providerVersionUri,
58-
Text: fmt.Sprintf("# %s Provider \n\n %s", provider["name"], providerDocs),
87+
if resp.StatusCode != http.StatusOK {
88+
resp.Body.Close()
89+
return nil, logAndReturnError(logger, fmt.Sprintf("Non-200 response fetching %s markdown", u.Name), fmt.Errorf("status: %s", resp.Status))
5990
}
91+
body, err := io.ReadAll(resp.Body)
92+
resp.Body.Close()
93+
if err != nil {
94+
return nil, logAndReturnError(logger, fmt.Sprintf("Error reading %s markdown", u.Name), err)
95+
}
96+
contents = append(contents, mcp.TextResourceContents{
97+
MIMEType: "text/markdown",
98+
URI: fmt.Sprintf("%s/%s", resourceURI, u.Name),
99+
Text: string(body),
100+
})
60101
}
61-
return resourceContents, nil
102+
return contents, nil
62103
}
63104
}

0 commit comments

Comments
 (0)