Skip to content

Commit f95836f

Browse files
Tool for enable config created and registered on mcp config (#1459)
* Tool for enable config created and registered on mcp config * fix: Added protection for missing traversal path and windonws syslink path transformation on enable config cmp tool * fix: Fix error to create dstDir if do not exists
1 parent 3876098 commit f95836f

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

mcp/config/config_enable.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/0xJacky/Nginx-UI/internal/config"
11+
"github.com/0xJacky/Nginx-UI/internal/helper"
12+
"github.com/0xJacky/Nginx-UI/internal/nginx"
13+
"github.com/mark3labs/mcp-go/mcp"
14+
)
15+
16+
const nginxConfigEnableToolName = "nginx_config_enable"
17+
18+
var nginxConfigEnableTool = mcp.NewTool(
19+
nginxConfigEnableToolName,
20+
mcp.WithDescription("Enable a previously created Nginx configuration (creates symlink in sites-enabled)"),
21+
mcp.WithString("name", mcp.Description("The name of the configuration file to enable")),
22+
mcp.WithString("base_dir", mcp.Description("The source directory (default: sites-available)")),
23+
mcp.WithBoolean("overwrite", mcp.Description("Whether to overwrite an existing enabled configuration")),
24+
)
25+
26+
func handleNginxConfigEnable(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
27+
args := request.GetArguments()
28+
name := args["name"].(string)
29+
baseDir := args["base_dir"].(string)
30+
overwrite := args["overwrite"].(bool)
31+
32+
if name == "" {
33+
return nil, fmt.Errorf("argument 'name' is required")
34+
}
35+
36+
// Default to sites-available if base_dir is not provided
37+
if baseDir == "" {
38+
baseDir = "sites-available"
39+
}
40+
41+
// Resolve Source Path (e.g., /etc/nginx/sites-available/my-site)
42+
// This is the file that must already exist.
43+
srcDir := nginx.GetConfPath(baseDir)
44+
srcPath := filepath.Join(srcDir, name)
45+
46+
// Ensure the resolved source path is actually inside the intended directory
47+
if !helper.IsUnderDirectory(srcPath, srcDir) {
48+
return nil, config.ErrPathIsNotUnderTheNginxConfDir
49+
}
50+
51+
// Validate Source Exists
52+
if _, err := os.Stat(srcPath); err != nil {
53+
return nil, fmt.Errorf("source configuration file not found at %s: %w", srcPath, err)
54+
}
55+
56+
sitesEnabledDir := nginx.GetConfPath("sites-enabled")
57+
dstPath := nginx.GetConfSymlinkPath(filepath.Join(sitesEnabledDir, name))
58+
59+
// Ensure the link we are about to create doesn't point outside sites-enabled
60+
if !helper.IsUnderDirectory(dstPath, sitesEnabledDir) {
61+
return nil, config.ErrPathIsNotUnderTheNginxConfDir
62+
}
63+
64+
// Ensure destination directory exists
65+
if !helper.FileExists(sitesEnabledDir) {
66+
if err := os.MkdirAll(sitesEnabledDir, 0755); err != nil {
67+
return nil, fmt.Errorf("failed to create sites-enabled directory: %w", err)
68+
}
69+
}
70+
71+
// Check if Destination Already Exists
72+
if helper.FileExists(dstPath) {
73+
if !overwrite {
74+
return nil, fmt.Errorf("configuration is already enabled (symlink exists at %s)", dstPath)
75+
}
76+
// Remove existing symlink/file if overwrite is true
77+
if err := os.Remove(dstPath); err != nil {
78+
return nil, fmt.Errorf("failed to remove existing configuration at %s: %w", dstPath, err)
79+
}
80+
}
81+
82+
// Create Symlink
83+
// We link srcPath -> dstPath
84+
if err := os.Symlink(srcPath, dstPath); err != nil {
85+
return nil, fmt.Errorf("failed to create symlink: %w", err)
86+
}
87+
88+
// Test Nginx Configuration
89+
// As per internal/site/enable.go, we must verify config before reloading
90+
res := nginx.Control(nginx.TestConfig)
91+
if res.IsError() {
92+
// Revert change (remove symlink) if test fails to prevent breaking Nginx
93+
os.Remove(dstPath)
94+
return nil, fmt.Errorf("nginx config test failed: %v", res.GetError())
95+
}
96+
97+
// Reload Nginx
98+
res = nginx.Control(nginx.Reload)
99+
if res.IsError() {
100+
return nil, fmt.Errorf("nginx reload failed: %v", res.GetError())
101+
}
102+
103+
// Construct Success Response
104+
result := map[string]string{
105+
"status": "success",
106+
"message": "Site enabled and Nginx reloaded successfully",
107+
"source": srcPath,
108+
"destination": dstPath,
109+
}
110+
jsonResult, _ := json.Marshal(result)
111+
112+
return mcp.NewToolResultText(string(jsonResult)), nil
113+
114+
}

mcp/config/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
func Init() {
88
mcp.AddTool(nginxConfigAddTool, handleNginxConfigAdd)
99
mcp.AddTool(nginxConfigBasePathTool, handleNginxConfigBasePath)
10+
mcp.AddTool(nginxConfigEnableTool, handleNginxConfigEnable)
1011
mcp.AddTool(nginxConfigGetTool, handleNginxConfigGet)
1112
mcp.AddTool(nginxConfigHistoryTool, handleNginxConfigHistory)
1213
mcp.AddTool(nginxConfigListTool, handleNginxConfigList)

0 commit comments

Comments
 (0)