mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
feat: mcp server
This commit is contained in:
parent
c4a9d03bb3
commit
e8ee931e16
51 changed files with 2749 additions and 1526 deletions
|
@ -1,7 +1,11 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
)
|
||||
|
||||
type ConfigsSort struct {
|
||||
|
@ -59,3 +63,59 @@ func Sort(key string, order string, configs []Config) []Config {
|
|||
|
||||
return configsSort.ConfigList
|
||||
}
|
||||
|
||||
func GetConfigList(relativePath string, filter func(file os.FileInfo) bool) ([]Config, error) {
|
||||
configFiles, err := os.ReadDir(nginx.GetConfPath(relativePath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configs := make([]Config, 0)
|
||||
|
||||
for i := range configFiles {
|
||||
file := configFiles[i]
|
||||
fileInfo, err := file.Info()
|
||||
if err != nil {
|
||||
logger.Error("Get File Info Error", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if filter != nil && !filter(fileInfo) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch mode := fileInfo.Mode(); {
|
||||
case mode.IsRegular(): // regular file, not a hidden file
|
||||
if "." == file.Name()[0:1] {
|
||||
continue
|
||||
}
|
||||
case mode&os.ModeSymlink != 0: // is a symbol
|
||||
var targetPath string
|
||||
targetPath, err = os.Readlink(nginx.GetConfPath(relativePath, file.Name()))
|
||||
if err != nil {
|
||||
logger.Error("Read Symlink Error", targetPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var targetInfo os.FileInfo
|
||||
targetInfo, err = os.Stat(targetPath)
|
||||
if err != nil {
|
||||
logger.Error("Stat Error", targetPath, err)
|
||||
continue
|
||||
}
|
||||
// hide the file if it's target file is a directory
|
||||
if targetInfo.IsDir() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
configs = append(configs, Config{
|
||||
Name: file.Name(),
|
||||
ModifiedAt: fileInfo.ModTime(),
|
||||
Size: fileInfo.Size(),
|
||||
IsDir: fileInfo.IsDir(),
|
||||
})
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
|
|
|
@ -340,7 +340,6 @@ func UpgradeStepThree() error {
|
|||
// Get old container name from environment variable, fallback to settings if not available
|
||||
currentContainerName := os.Getenv("NGINX_UI_CONTAINER_NAME")
|
||||
if currentContainerName == "" {
|
||||
logger.Warn("Old container name not found in environment, skipping cleanup")
|
||||
return nil
|
||||
}
|
||||
oldContainerName := currentContainerName + OldSuffix
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/0xJacky/Nginx-UI/internal/cluster"
|
||||
"github.com/0xJacky/Nginx-UI/internal/cron"
|
||||
"github.com/0xJacky/Nginx-UI/internal/docker"
|
||||
"github.com/0xJacky/Nginx-UI/internal/mcp"
|
||||
"github.com/0xJacky/Nginx-UI/internal/passkey"
|
||||
"github.com/0xJacky/Nginx-UI/internal/validation"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
|
@ -61,6 +62,7 @@ func InitAfterDatabase() {
|
|||
analytic.RetrieveNodesStatus,
|
||||
passkey.Init,
|
||||
RegisterAcmeUser,
|
||||
mcp.Init,
|
||||
}
|
||||
|
||||
for _, v := range syncs {
|
||||
|
@ -138,7 +140,5 @@ func CheckAndCleanupOTAContainers() {
|
|||
err := docker.UpgradeStepThree()
|
||||
if err != nil {
|
||||
logger.Error("Failed to cleanup OTA containers:", err)
|
||||
} else {
|
||||
logger.Info("OTA container cleanup completed successfully")
|
||||
}
|
||||
}
|
||||
|
|
60
internal/mcp/server.go
Normal file
60
internal/mcp/server.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package mcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
var (
|
||||
mcpServer = server.NewMCPServer(
|
||||
"Nginx",
|
||||
"1.0.0",
|
||||
server.WithResourceCapabilities(true, true),
|
||||
server.WithLogging(),
|
||||
server.WithRecovery(),
|
||||
)
|
||||
sseServer = server.NewSSEServer(
|
||||
mcpServer,
|
||||
server.WithSSEEndpoint("/mcp"),
|
||||
server.WithMessageEndpoint("/mcp_message"),
|
||||
)
|
||||
)
|
||||
|
||||
const (
|
||||
MimeTypeJSON = "application/json"
|
||||
MimeTypeText = "text/plain"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
Resource mcp.Resource
|
||||
Handler func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error)
|
||||
}
|
||||
|
||||
type Tool struct {
|
||||
Tool mcp.Tool
|
||||
Handler func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
|
||||
}
|
||||
|
||||
var (
|
||||
tools = []Tool{}
|
||||
toolMutex sync.Mutex
|
||||
)
|
||||
func AddTool(tool mcp.Tool, handler func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)) {
|
||||
toolMutex.Lock()
|
||||
defer toolMutex.Unlock()
|
||||
tools = append(tools, Tool{Tool: tool, Handler: handler})
|
||||
}
|
||||
|
||||
func ServeHTTP(c *gin.Context) {
|
||||
sseServer.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
func Init() {
|
||||
for _, tool := range tools {
|
||||
mcpServer.AddTool(tool.Tool, tool.Handler)
|
||||
}
|
||||
}
|
|
@ -59,6 +59,12 @@ func AuthRequired() gin.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
if token := c.Query("node_secret"); token != "" && token == settings.NodeSettings.Secret {
|
||||
c.Set("Secret", token)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
token := getToken(c)
|
||||
if token == "" {
|
||||
abortWithAuthFailure()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue