Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 119 additions & 7 deletions pkg/mcp/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,29 @@ func NewServer() *MCPServer {
handleDeleteTool,
)

mcpServer.AddTool(
mcp.NewTool("config_volumes",
mcp.WithDescription("Lists and manages configured volumes for a function"),
mcp.WithString("action",
mcp.Required(),
mcp.Description("The action to perform: 'add' to add a volume, 'remove' to remove a volume, 'list' to list volumes"),
),
mcp.WithString("path",
mcp.Required(),
mcp.Description("Path to the function. Default is current directory ($FUNC_PATH)"),
),

// Optional flags
mcp.WithString("type", mcp.Description("Volume type: configmap, secret, pvc, or emptydir")),
mcp.WithString("mount_path", mcp.Description("Mount path for the volume in the function container")),
mcp.WithString("source", mcp.Description("Name of the ConfigMap, Secret, or PVC to mount (not used for emptydir)")),
mcp.WithString("medium", mcp.Description("Storage medium for EmptyDir volume: 'Memory' or '' (default)")),
mcp.WithString("size", mcp.Description("Maximum size limit for EmptyDir volume (e.g., 1Gi)")),
mcp.WithBoolean("read_only", mcp.Description("Mount volume as read-only (only for PVC)")),
mcp.WithBoolean("verbose", mcp.Description("Print verbose logs ($FUNC_VERBOSE)")),
),
handleConfigVolumesTool,
)
mcpServer.AddResource(mcp.NewResource(
"func://docs",
"Root Help Command",
Expand All @@ -170,7 +193,7 @@ func NewServer() *MCPServer {
mcp.WithResourceDescription("--help output of the 'create' command"),
mcp.WithMIMEType("text/plain"),
), func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
return runHelpCommand("create", "func://create/docs")
return runHelpCommand([]string{"create"}, "func://create/docs")
})

mcpServer.AddResource(mcp.NewResource(
Expand All @@ -179,7 +202,7 @@ func NewServer() *MCPServer {
mcp.WithResourceDescription("--help output of the 'build' command"),
mcp.WithMIMEType("text/plain"),
), func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
return runHelpCommand("build", "func://build/docs")
return runHelpCommand([]string{"build"}, "func://build/docs")
})

mcpServer.AddResource(mcp.NewResource(
Expand All @@ -188,7 +211,7 @@ func NewServer() *MCPServer {
mcp.WithResourceDescription("--help output of the 'deploy' command"),
mcp.WithMIMEType("text/plain"),
), func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
return runHelpCommand("deploy", "func://deploy/docs")
return runHelpCommand([]string{"deploy"}, "func://deploy/docs")
})

mcpServer.AddResource(mcp.NewResource(
Expand All @@ -197,7 +220,7 @@ func NewServer() *MCPServer {
mcp.WithResourceDescription("--help output of the 'list' command"),
mcp.WithMIMEType("text/plain"),
), func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
return runHelpCommand("list", "func://list/docs")
return runHelpCommand([]string{"list"}, "func://list/docs")
})

mcpServer.AddResource(mcp.NewResource(
Expand All @@ -206,7 +229,25 @@ func NewServer() *MCPServer {
mcp.WithResourceDescription("--help output of the 'delete' command"),
mcp.WithMIMEType("text/plain"),
), func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
return runHelpCommand("delete", "func://delete/docs")
return runHelpCommand([]string{"delete"}, "func://delete/docs")
})

mcpServer.AddResource(mcp.NewResource(
"func://config/volumes/add/docs",
"Config Volumes Add Command Help",
mcp.WithResourceDescription("--help output of the 'config volumes add' command"),
mcp.WithMIMEType("text/plain"),
), func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
return runHelpCommand([]string{"config", "volumes", "add"}, "func://config/volumes/add/docs")
})

mcpServer.AddResource(mcp.NewResource(
"func://config/volumes/remove/docs",
"Config Volumes Remove Command Help",
mcp.WithResourceDescription("--help output of the 'config volumes remove' command"),
mcp.WithMIMEType("text/plain"),
), func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
return runHelpCommand([]string{"config", "volumes", "remove"}, "func://config/volumes/add/docs")
})

mcpServer.AddPrompt(mcp.NewPrompt("help",
Expand Down Expand Up @@ -245,8 +286,9 @@ func handleRootHelpResource(ctx context.Context, request mcp.ReadResourceRequest
}, nil
}

func runHelpCommand(cmd string, uri string) ([]mcp.ResourceContents, error) {
content, err := exec.Command("func", cmd, "--help").Output()
func runHelpCommand(args []string, uri string) ([]mcp.ResourceContents, error) {
args = append(args, "--help")
content, err := exec.Command("func", args...).Output()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -573,3 +615,73 @@ func handleDeleteTool(
body := []byte(fmt.Sprintf(`{"result": "%s"}`, out))
return mcp.NewToolResultText(string(body)), nil
}

func handleConfigVolumesTool(
ctx context.Context,
request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
action, err := request.RequireString("action")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
path, err := request.RequireString("path")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
if action == "list" {
// For 'list' action, we don't need other parameters, only --path
args := []string{"config", "volumes", "--path", path}
if request.GetBool("verbose", false) {
args = append(args, "--verbose")
}

cmd := exec.Command("func", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("func config volumes list failed: %s", out)), nil
}
body := []byte(fmt.Sprintf(`{"result": "%s"}`, out))
return mcp.NewToolResultText(string(body)), nil
}

args := []string{"config", "volumes", action}

if action == "add" {
volumeType, err := request.RequireString("type")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
args = append(args, "--type", volumeType)
}
mountPath, err := request.RequireString("mount_path")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
args = append(args, "--mount-path", mountPath, "--path", path)

// Optional flags
if v := request.GetString("source", ""); v != "" {
args = append(args, "--source", v)
}
if v := request.GetString("medium", ""); v != "" {
args = append(args, "--medium", v)
}
if v := request.GetString("size", ""); v != "" {
args = append(args, "--size", v)
}
if request.GetBool("read_only", false) {
args = append(args, "--read-only")
}
if request.GetBool("verbose", false) {
args = append(args, "--verbose")
}

cmd := exec.Command("func", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("func config volumes failed: %s", out)), nil
}

body := []byte(fmt.Sprintf(`{"result": "%s"}`, out))
return mcp.NewToolResultText(string(body)), nil
}
Loading