diff --git a/README.md b/README.md index 657ebf0..d3481da 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ port: 8080 base_url: https://your-domain.com authorization_server_url: https://your-auth-server.com api_base_url: https://api.createos.nodeops.network +sandbox_api_base_url: https://your-fc-control-api.example.com transport: http # "http" or "stdio" log_level: debug @@ -220,6 +221,9 @@ The server exposes **85+ tools** organized into the following categories: | **GitHub** | `InstallGithubApp`, `ListConnectedGithubAccounts`, `ListGithubRepositories`, `ListGithubRepositoryBranches`, `GetGithubRepositoryContent` | GitHub integration for repo-based deployments | | **Account** | `GetCurrentUser`, `GetQuotas`, `GetSupportedProjectTypes` | User info, quotas, and platform capabilities | | **Transfers** | `TransferProject`, `GetProjectTransferUri`, `ListProjectTransferHistory` | Transfer project ownership between accounts | +| **Sandboxes** | `CreateSandbox`, `UpdateSandbox`, `ExecSandbox`, `DeleteSandbox` | Create, update, execute commands in, and destroy sandbox VMs | +| **Sandbox Disks** | `CreateDisk`, `ListDisks`, `GetDisk`, `DeleteDisk`, `AttachSandboxDisk`, `DetachSandboxDisk`, `ListSandboxDisks` | Register S3 disks and attach them to sandboxes | +| **Sandbox Networks** | `CreateNetwork`, `ListNetworks`, `GetNetwork`, `DeleteNetwork`, `AttachSandboxNetwork`, `DetachSandboxNetwork` | Create private sandbox networks and manage membership | --- diff --git a/config-files/config.yaml b/config-files/config.yaml index d684852..7ce3b83 100644 --- a/config-files/config.yaml +++ b/config-files/config.yaml @@ -2,6 +2,7 @@ port: 8080 base_url: authorization_server_url: api_base_url: +sandbox_api_base_url: transport: http log_level: debug diff --git a/config/config.go b/config/config.go index e538754..99ed47f 100644 --- a/config/config.go +++ b/config/config.go @@ -12,6 +12,7 @@ type Config struct { BaseURL string `yaml:"base_url"` AuthorizationServerUrl string `yaml:"authorization_server_url"` APIBaseUrl string `yaml:"api_base_url"` + SandboxAPIBaseUrl string `yaml:"sandbox_api_base_url"` Transport string `yaml:"transport"` LogLevel string `yaml:"log_level"` diff --git a/handlers/AttachSandboxDisk.go b/handlers/AttachSandboxDisk.go new file mode 100644 index 0000000..b3c3336 --- /dev/null +++ b/handlers/AttachSandboxDisk.go @@ -0,0 +1,28 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type AttachSandboxDiskParams struct { + ID string `json:"id"` + Body map[string]interface{} `json:"body"` +} + +func AttachSandboxDiskHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[AttachSandboxDiskParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxPostRequest(fmt.Sprintf("/v1/sandboxes/%s/disks", params.ID), params.Body, authInfo) +} diff --git a/handlers/AttachSandboxNetwork.go b/handlers/AttachSandboxNetwork.go new file mode 100644 index 0000000..e09a018 --- /dev/null +++ b/handlers/AttachSandboxNetwork.go @@ -0,0 +1,28 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type AttachSandboxNetworkParams struct { + ID string `json:"id"` + Body map[string]interface{} `json:"body"` +} + +func AttachSandboxNetworkHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[AttachSandboxNetworkParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxPostRequest(fmt.Sprintf("/v1/sandboxes/%s/networks", params.ID), params.Body, authInfo) +} diff --git a/handlers/CreateDisk.go b/handlers/CreateDisk.go new file mode 100644 index 0000000..f9abe2e --- /dev/null +++ b/handlers/CreateDisk.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type CreateDiskParams struct { + Body map[string]interface{} `json:"body"` +} + +func CreateDiskHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[CreateDiskParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxPostRequest("/v1/disks", params.Body, authInfo) +} diff --git a/handlers/CreateNetwork.go b/handlers/CreateNetwork.go new file mode 100644 index 0000000..bf97fc5 --- /dev/null +++ b/handlers/CreateNetwork.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type CreateNetworkParams struct { + Body map[string]interface{} `json:"body"` +} + +func CreateNetworkHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[CreateNetworkParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxPostRequest("/v1/networks", params.Body, authInfo) +} diff --git a/handlers/CreateSandbox.go b/handlers/CreateSandbox.go new file mode 100644 index 0000000..016da76 --- /dev/null +++ b/handlers/CreateSandbox.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type CreateSandboxParams struct { + Body map[string]interface{} `json:"body"` +} + +func CreateSandboxHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[CreateSandboxParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxPostRequest("/v1/sandboxes", params.Body, authInfo) +} diff --git a/handlers/DeleteDisk.go b/handlers/DeleteDisk.go new file mode 100644 index 0000000..afe6704 --- /dev/null +++ b/handlers/DeleteDisk.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type DeleteDiskParams struct { + IDOrName string `json:"id_or_name"` +} + +func DeleteDiskHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[DeleteDiskParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxDeleteRequest(fmt.Sprintf("/v1/disks/%s", params.IDOrName), authInfo) +} diff --git a/handlers/DeleteNetwork.go b/handlers/DeleteNetwork.go new file mode 100644 index 0000000..439e322 --- /dev/null +++ b/handlers/DeleteNetwork.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type DeleteNetworkParams struct { + ID string `json:"id"` +} + +func DeleteNetworkHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[DeleteNetworkParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxDeleteRequest(fmt.Sprintf("/v1/networks/%s", params.ID), authInfo) +} diff --git a/handlers/DeleteSandbox.go b/handlers/DeleteSandbox.go new file mode 100644 index 0000000..f76dd02 --- /dev/null +++ b/handlers/DeleteSandbox.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type DeleteSandboxParams struct { + ID string `json:"id"` +} + +func DeleteSandboxHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[DeleteSandboxParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxDeleteRequest(fmt.Sprintf("/v1/sandboxes/%s", params.ID), authInfo) +} diff --git a/handlers/DetachSandboxDisk.go b/handlers/DetachSandboxDisk.go new file mode 100644 index 0000000..0232fd2 --- /dev/null +++ b/handlers/DetachSandboxDisk.go @@ -0,0 +1,37 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type DetachSandboxDiskParams struct { + ID string `json:"id"` + DiskID string `json:"disk_id"` + MountPath string `json:"mount_path"` +} + +func DetachSandboxDiskHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[DetachSandboxDiskParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + queryParams := map[string]string{ + "mount_path": params.MountPath, + } + + return makeSandboxDeleteRequestWithQuery( + fmt.Sprintf("/v1/sandboxes/%s/disks/%s", params.ID, params.DiskID), + queryParams, + authInfo, + ) +} diff --git a/handlers/DetachSandboxNetwork.go b/handlers/DetachSandboxNetwork.go new file mode 100644 index 0000000..8a9a4da --- /dev/null +++ b/handlers/DetachSandboxNetwork.go @@ -0,0 +1,28 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type DetachSandboxNetworkParams struct { + ID string `json:"id"` + Network string `json:"network"` +} + +func DetachSandboxNetworkHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[DetachSandboxNetworkParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxDeleteRequest(fmt.Sprintf("/v1/sandboxes/%s/networks/%s", params.ID, params.Network), authInfo) +} diff --git a/handlers/ExecSandbox.go b/handlers/ExecSandbox.go new file mode 100644 index 0000000..f2fd973 --- /dev/null +++ b/handlers/ExecSandbox.go @@ -0,0 +1,31 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type ExecSandboxParams struct { + ID string `json:"id"` + Body map[string]interface{} `json:"body"` +} + +func ExecSandboxHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[ExecSandboxParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + if stream, ok := params.Body["stream"].(bool); ok && stream { + return nil, fmt.Errorf("streaming exec is not supported by ExecSandbox yet; omit stream or set it to false") + } + + return makeSandboxPostRequest(fmt.Sprintf("/v1/sandboxes/%s/exec", params.ID), params.Body, authInfo) +} diff --git a/handlers/GetDisk.go b/handlers/GetDisk.go new file mode 100644 index 0000000..3d0d29e --- /dev/null +++ b/handlers/GetDisk.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type GetDiskParams struct { + IDOrName string `json:"id_or_name"` +} + +func GetDiskHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[GetDiskParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxGetRequest(fmt.Sprintf("/v1/disks/%s", params.IDOrName), nil, authInfo) +} diff --git a/handlers/GetNetwork.go b/handlers/GetNetwork.go new file mode 100644 index 0000000..3e337e6 --- /dev/null +++ b/handlers/GetNetwork.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type GetNetworkParams struct { + ID string `json:"id"` +} + +func GetNetworkHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[GetNetworkParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxGetRequest(fmt.Sprintf("/v1/networks/%s", params.ID), nil, authInfo) +} diff --git a/handlers/ListDisks.go b/handlers/ListDisks.go new file mode 100644 index 0000000..4c95b56 --- /dev/null +++ b/handlers/ListDisks.go @@ -0,0 +1,37 @@ +package handler + +import ( + "context" + "fmt" + "strconv" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type ListDisksParams struct { + Limit *int `json:"limit"` + Offset *int `json:"offset"` +} + +func ListDisksHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[ListDisksParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + queryParams := make(map[string]string) + if params.Limit != nil { + queryParams["limit"] = strconv.Itoa(*params.Limit) + } + if params.Offset != nil { + queryParams["offset"] = strconv.Itoa(*params.Offset) + } + + return makeSandboxGetRequest("/v1/disks", queryParams, authInfo) +} diff --git a/handlers/ListNetworks.go b/handlers/ListNetworks.go new file mode 100644 index 0000000..e1c64bc --- /dev/null +++ b/handlers/ListNetworks.go @@ -0,0 +1,37 @@ +package handler + +import ( + "context" + "fmt" + "strconv" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type ListNetworksParams struct { + Limit *int `json:"limit"` + Offset *int `json:"offset"` +} + +func ListNetworksHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[ListNetworksParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + queryParams := make(map[string]string) + if params.Limit != nil { + queryParams["limit"] = strconv.Itoa(*params.Limit) + } + if params.Offset != nil { + queryParams["offset"] = strconv.Itoa(*params.Offset) + } + + return makeSandboxGetRequest("/v1/networks", queryParams, authInfo) +} diff --git a/handlers/ListSandboxDisks.go b/handlers/ListSandboxDisks.go new file mode 100644 index 0000000..e7e9424 --- /dev/null +++ b/handlers/ListSandboxDisks.go @@ -0,0 +1,27 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type ListSandboxDisksParams struct { + ID string `json:"id"` +} + +func ListSandboxDisksHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[ListSandboxDisksParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxGetRequest(fmt.Sprintf("/v1/sandboxes/%s/disks", params.ID), nil, authInfo) +} diff --git a/handlers/UpdateSandbox.go b/handlers/UpdateSandbox.go new file mode 100644 index 0000000..33c8729 --- /dev/null +++ b/handlers/UpdateSandbox.go @@ -0,0 +1,28 @@ +package handler + +import ( + "context" + "fmt" + + mcputils "github.com/NodeOps-app/createos-mcp/helpers" + "github.com/mark3labs/mcp-go/mcp" +) + +type UpdateSandboxParams struct { + ID string `json:"id"` + Body map[string]interface{} `json:"body"` +} + +func UpdateSandboxHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + authInfo, args, err := handleRequest(ctx, request) + if err != nil { + return nil, err + } + + params, err := mcputils.ParamsParser[UpdateSandboxParams](args) + if err != nil { + return nil, fmt.Errorf("failed to parse parameters: %w", err) + } + + return makeSandboxPatchRequest(fmt.Sprintf("/v1/sandboxes/%s", params.ID), params.Body, authInfo) +} diff --git a/handlers/helpers.go b/handlers/helpers.go index e54714b..32b5327 100644 --- a/handlers/helpers.go +++ b/handlers/helpers.go @@ -86,3 +86,59 @@ func makeDeleteRequest(path string, authInfo *AuthInfo) (*mcp.CallToolResult, er }, }, nil } + +func makeSandboxPostRequest(path string, body interface{}, authInfo *AuthInfo) (*mcp.CallToolResult, error) { + resp, err := mcputils.SandboxPost(path, body, authInfo.Method, authInfo.Value) + if err != nil { + return nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(string(resp.Body())), + }, + }, nil +} + +func makeSandboxGetRequest(path string, queryParams map[string]string, authInfo *AuthInfo) (*mcp.CallToolResult, error) { + resp, err := mcputils.SandboxGet(path, queryParams, authInfo.Method, authInfo.Value) + if err != nil { + return nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(string(resp.Body())), + }, + }, nil +} + +func makeSandboxPatchRequest(path string, body interface{}, authInfo *AuthInfo) (*mcp.CallToolResult, error) { + resp, err := mcputils.SandboxPatch(path, body, authInfo.Method, authInfo.Value) + if err != nil { + return nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(string(resp.Body())), + }, + }, nil +} + +func makeSandboxDeleteRequest(path string, authInfo *AuthInfo) (*mcp.CallToolResult, error) { + return makeSandboxDeleteRequestWithQuery(path, nil, authInfo) +} + +func makeSandboxDeleteRequestWithQuery(path string, queryParams map[string]string, authInfo *AuthInfo) (*mcp.CallToolResult, error) { + resp, err := mcputils.SandboxDelete(path, queryParams, authInfo.Method, authInfo.Value) + if err != nil { + return nil, err + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(string(resp.Body())), + }, + }, nil +} diff --git a/helpers/httpclient.go b/helpers/httpclient.go index 49e3cb5..77e415f 100644 --- a/helpers/httpclient.go +++ b/helpers/httpclient.go @@ -11,16 +11,39 @@ import ( // Client returns a configured Resty client with base URL and auth token func Client() *resty.Client { - baseURL := config.Cfg.APIBaseUrl - client := resty.New(). - SetBaseURL(baseURL). + SetBaseURL(config.Cfg.APIBaseUrl). SetHeader("Content-Type", "application/json"). SetHeader("Accept", "application/json") return client } +func SandboxClient() (*resty.Client, error) { + if config.Cfg.SandboxAPIBaseUrl == "" { + return nil, fmt.Errorf("sandbox_api_base_url is required for sandbox tools") + } + + client := resty.New(). + SetBaseURL(config.Cfg.SandboxAPIBaseUrl). + SetHeader("Content-Type", "application/json"). + SetHeader("Accept", "application/json") + + return client, nil +} + +func setAuth(req *resty.Request, authMethod string, authValue string) error { + switch authMethod { + case "api-key": + req.SetHeader("X-Api-Key", authValue) + case "bearer-token": + req.SetHeader("X-Access-Token", authValue) + default: + return fmt.Errorf("unsupported auth method: %s", authMethod) + } + return nil +} + // Get makes a GET request with authentication func Get(path string, queryParams map[string]string, authMethod string, authValue string) (*resty.Response, error) { req := Client().R() @@ -180,3 +203,97 @@ func DeleteWithBody(path string, body interface{}, authMethod string, authValue return resp, nil } + +// SandboxPost makes a POST request to the sandbox API with authentication. +func SandboxPost(path string, body interface{}, authMethod string, authValue string) (*resty.Response, error) { + client, err := SandboxClient() + if err != nil { + return nil, err + } + req := client.R().SetBody(body) + if err := setAuth(req, authMethod, authValue); err != nil { + return nil, err + } + + resp, err := req.Post(path) + if err != nil { + return nil, fmt.Errorf("sandbox POST request failed: %w", err) + } + if resp.IsError() { + return resp, fmt.Errorf("sandbox API error (status %d)", resp.StatusCode()) + } + + return resp, nil +} + +// SandboxGet makes a GET request to the sandbox API with authentication. +func SandboxGet(path string, queryParams map[string]string, authMethod string, authValue string) (*resty.Response, error) { + client, err := SandboxClient() + if err != nil { + return nil, err + } + req := client.R() + if err := setAuth(req, authMethod, authValue); err != nil { + return nil, err + } + for key, value := range queryParams { + req.SetQueryParam(key, value) + } + + resp, err := req.Get(path) + if err != nil { + return nil, fmt.Errorf("sandbox GET request failed: %w", err) + } + if resp.IsError() { + return resp, fmt.Errorf("sandbox API error (status %d)", resp.StatusCode()) + } + + return resp, nil +} + +// SandboxPatch makes a PATCH request to the sandbox API with authentication. +func SandboxPatch(path string, body interface{}, authMethod string, authValue string) (*resty.Response, error) { + client, err := SandboxClient() + if err != nil { + return nil, err + } + req := client.R().SetBody(body) + if err := setAuth(req, authMethod, authValue); err != nil { + return nil, err + } + + resp, err := req.Patch(path) + if err != nil { + return nil, fmt.Errorf("sandbox PATCH request failed: %w", err) + } + if resp.IsError() { + return resp, fmt.Errorf("sandbox API error (status %d)", resp.StatusCode()) + } + + return resp, nil +} + +// SandboxDelete makes a DELETE request to the sandbox API with authentication. +func SandboxDelete(path string, queryParams map[string]string, authMethod string, authValue string) (*resty.Response, error) { + client, err := SandboxClient() + if err != nil { + return nil, err + } + req := client.R() + if err := setAuth(req, authMethod, authValue); err != nil { + return nil, err + } + for key, value := range queryParams { + req.SetQueryParam(key, value) + } + + resp, err := req.Delete(path) + if err != nil { + return nil, fmt.Errorf("sandbox DELETE request failed: %w", err) + } + if resp.IsError() { + return resp, fmt.Errorf("sandbox API error (status %d)", resp.StatusCode()) + } + + return resp, nil +} diff --git a/mcptools/AttachSandboxDisk.go b/mcptools/AttachSandboxDisk.go new file mode 100644 index 0000000..e4b5bdc --- /dev/null +++ b/mcptools/AttachSandboxDisk.go @@ -0,0 +1,43 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const attachSandboxDiskInputSchema = `{ + "properties": { + "id": { + "description": "Sandbox identifier.", + "type": "string" + }, + "body": { + "properties": { + "disk_id": { + "description": "Disk id or user-scoped disk name.", + "example": "my-data", + "type": "string" + }, + "mount_path": { + "description": "Absolute mount path inside the sandbox guest.", + "example": "/mnt/data", + "type": "string" + }, + "sub_path": { + "description": "Optional bucket prefix to mount.", + "example": "team-a/", + "type": "string" + } + }, + "required": ["disk_id", "mount_path"], + "type": "object" + } + }, + "required": ["id", "body"], + "type": "object" +}` + +func NewAttachSandboxDiskMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "AttachSandboxDisk", + "Live-attach a disk to a running sandbox.", + []byte(attachSandboxDiskInputSchema), + ) +} diff --git a/mcptools/AttachSandboxNetwork.go b/mcptools/AttachSandboxNetwork.go new file mode 100644 index 0000000..c3f2aea --- /dev/null +++ b/mcptools/AttachSandboxNetwork.go @@ -0,0 +1,32 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const attachSandboxNetworkInputSchema = `{ + "properties": { + "id": { + "description": "Sandbox identifier.", + "type": "string" + }, + "body": { + "properties": { + "id": { + "description": "Network name or net- id.", + "type": "string" + } + }, + "required": ["id"], + "type": "object" + } + }, + "required": ["id", "body"], + "type": "object" +}` + +func NewAttachSandboxNetworkMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "AttachSandboxNetwork", + "Attach a sandbox to a private network.", + []byte(attachSandboxNetworkInputSchema), + ) +} diff --git a/mcptools/CreateDisk.go b/mcptools/CreateDisk.go new file mode 100644 index 0000000..e76ea31 --- /dev/null +++ b/mcptools/CreateDisk.go @@ -0,0 +1,53 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const createDiskInputSchema = `{ + "properties": { + "body": { + "properties": { + "name": { + "description": "User-scoped disk name. Lowercase alphanumeric plus dash, 1 to 63 chars.", + "pattern": "^[a-z0-9][a-z0-9-]{0,62}$", + "example": "my-data", + "type": "string" + }, + "kind": { + "description": "Disk backend kind. Currently only s3 is supported.", + "enum": ["s3"], + "type": "string" + }, + "config": { + "properties": { + "bucket": { "type": "string", "example": "my-data-bucket" }, + "endpoint": { "type": "string", "example": "https://s3.amazonaws.com" }, + "region": { "type": "string", "example": "us-east-1" }, + "use_path_style": { "type": "boolean" } + }, + "required": ["bucket", "endpoint"], + "type": "object" + }, + "credentials": { + "properties": { + "access_key": { "type": "string" }, + "secret_key": { "format": "password", "type": "string" } + }, + "required": ["access_key", "secret_key"], + "type": "object" + } + }, + "required": ["name", "kind", "config", "credentials"], + "type": "object" + } + }, + "required": ["body"], + "type": "object" +}` + +func NewCreateDiskMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "CreateDisk", + "Register an S3-compatible bucket as a sandbox disk.", + []byte(createDiskInputSchema), + ) +} diff --git a/mcptools/CreateNetwork.go b/mcptools/CreateNetwork.go new file mode 100644 index 0000000..84fefe3 --- /dev/null +++ b/mcptools/CreateNetwork.go @@ -0,0 +1,29 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const createNetworkInputSchema = `{ + "properties": { + "body": { + "properties": { + "name": { + "description": "User-facing network name.", + "example": "backend", + "type": "string" + } + }, + "required": ["name"], + "type": "object" + } + }, + "required": ["body"], + "type": "object" +}` + +func NewCreateNetworkMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "CreateNetwork", + "Create a private sandbox network.", + []byte(createNetworkInputSchema), + ) +} diff --git a/mcptools/CreateSandbox.go b/mcptools/CreateSandbox.go new file mode 100644 index 0000000..43e22fe --- /dev/null +++ b/mcptools/CreateSandbox.go @@ -0,0 +1,103 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +// Input Schema for the CreateSandbox tool +const createSandboxInputSchema = `{ + "properties": { + "body": { + "properties": { + "shape": { + "description": "Required sandbox shape. Use the sandbox API's GET /v1/shapes endpoint to discover available values.", + "example": "s-1vcpu-256mb", + "type": "string" + }, + "rootfs": { + "description": "Root filesystem catalog name. Empty means host default.", + "example": "devbox:1", + "type": "string" + }, + "name": { + "description": "User-facing sandbox name, unique per user among non-terminal sandboxes. Omit to auto-generate.", + "example": "brave-otter", + "type": "string" + }, + "networks": { + "description": "Private networks to join at create time. Each entry is {\"id\":\"\"}.", + "items": { + "properties": { + "id": { "type": "string" } + }, + "required": ["id"], + "type": "object" + }, + "type": "array" + }, + "disk_mib": { + "description": "Disk size in MiB. 0 or omitted uses the shape default.", + "format": "int64", + "type": "integer" + }, + "egress": { + "description": "Network egress allowlist entries. Use host[:port], ip[:port], cidr[:port], or *.", + "items": { "type": "string" }, + "type": "array" + }, + "envs": { + "additionalProperties": { "type": "string" }, + "description": "Environment variables exported into every exec invocation. Keys must be declared here before exec overrides can use them.", + "type": "object" + }, + "ssh_pubkeys": { + "description": "OpenSSH public keys authorized for SSH gateway tunnel/shell access.", + "items": { "type": "string" }, + "type": "array" + }, + "host_id": { + "description": "Optional host pin. Empty lets the scheduler choose.", + "type": "string" + }, + "region": { + "description": "Optional placement region.", + "example": "us", + "type": "string" + }, + "ingress_enabled": { + "description": "Whether HTTP ingress is enabled for this sandbox.", + "type": "boolean" + }, + "auto_pause_after_seconds": { + "description": "Idle timeout in seconds. Valid range is 60 to 86400. Null or omitted disables auto-pause.", + "maximum": 86400, + "minimum": 60, + "type": ["integer", "null"] + }, + "disks": { + "description": "S3 disks to mount at create time.", + "items": { + "properties": { + "disk_id": { "type": "string" }, + "mount_path": { "type": "string" } + }, + "required": ["disk_id", "mount_path"], + "type": "object" + }, + "type": "array" + } + }, + "required": ["shape"], + "type": "object" + } + }, + "required": ["body"], + "type": "object" +}` + +// NewCreateSandboxMCPTool creates the MCP Tool instance for CreateSandbox +func NewCreateSandboxMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "CreateSandbox", + "Create a sandbox VM in the sandbox control plane.", + []byte(createSandboxInputSchema), + ) +} diff --git a/mcptools/DeleteDisk.go b/mcptools/DeleteDisk.go new file mode 100644 index 0000000..7635e07 --- /dev/null +++ b/mcptools/DeleteDisk.go @@ -0,0 +1,22 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const deleteDiskInputSchema = `{ + "properties": { + "id_or_name": { + "description": "Disk id, such as disk_, or user-scoped disk name.", + "type": "string" + } + }, + "required": ["id_or_name"], + "type": "object" +}` + +func NewDeleteDiskMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "DeleteDisk", + "Soft-delete a sandbox disk. Bucket contents are not touched.", + []byte(deleteDiskInputSchema), + ) +} diff --git a/mcptools/DeleteNetwork.go b/mcptools/DeleteNetwork.go new file mode 100644 index 0000000..dd7a128 --- /dev/null +++ b/mcptools/DeleteNetwork.go @@ -0,0 +1,22 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const deleteNetworkInputSchema = `{ + "properties": { + "id": { + "description": "Network name or net- id.", + "type": "string" + } + }, + "required": ["id"], + "type": "object" +}` + +func NewDeleteNetworkMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "DeleteNetwork", + "Delete a private sandbox network. The network must have no active members.", + []byte(deleteNetworkInputSchema), + ) +} diff --git a/mcptools/DeleteSandbox.go b/mcptools/DeleteSandbox.go new file mode 100644 index 0000000..4e43922 --- /dev/null +++ b/mcptools/DeleteSandbox.go @@ -0,0 +1,24 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +// Input Schema for the DeleteSandbox tool +const deleteSandboxInputSchema = `{ + "properties": { + "id": { + "description": "Sandbox identifier.", + "type": "string" + } + }, + "required": ["id"], + "type": "object" +}` + +// NewDeleteSandboxMCPTool creates the MCP Tool instance for DeleteSandbox +func NewDeleteSandboxMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "DeleteSandbox", + "Destroy a sandbox VM. The backend treats deleting an already terminal sandbox idempotently.", + []byte(deleteSandboxInputSchema), + ) +} diff --git a/mcptools/DetachSandboxDisk.go b/mcptools/DetachSandboxDisk.go new file mode 100644 index 0000000..c0c67b0 --- /dev/null +++ b/mcptools/DetachSandboxDisk.go @@ -0,0 +1,31 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const detachSandboxDiskInputSchema = `{ + "properties": { + "id": { + "description": "Sandbox identifier.", + "type": "string" + }, + "disk_id": { + "description": "Disk id, always disk_ for detach.", + "type": "string" + }, + "mount_path": { + "description": "Absolute mount path for the attachment to detach.", + "example": "/mnt/data", + "type": "string" + } + }, + "required": ["id", "disk_id", "mount_path"], + "type": "object" +}` + +func NewDetachSandboxDiskMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "DetachSandboxDisk", + "Detach one disk mount from a sandbox. Bucket contents are not touched.", + []byte(detachSandboxDiskInputSchema), + ) +} diff --git a/mcptools/DetachSandboxNetwork.go b/mcptools/DetachSandboxNetwork.go new file mode 100644 index 0000000..ea38397 --- /dev/null +++ b/mcptools/DetachSandboxNetwork.go @@ -0,0 +1,26 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const detachSandboxNetworkInputSchema = `{ + "properties": { + "id": { + "description": "Sandbox identifier.", + "type": "string" + }, + "network": { + "description": "Network name or net- id.", + "type": "string" + } + }, + "required": ["id", "network"], + "type": "object" +}` + +func NewDetachSandboxNetworkMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "DetachSandboxNetwork", + "Detach a sandbox from a private network.", + []byte(detachSandboxNetworkInputSchema), + ) +} diff --git a/mcptools/ExecSandbox.go b/mcptools/ExecSandbox.go new file mode 100644 index 0000000..06d1722 --- /dev/null +++ b/mcptools/ExecSandbox.go @@ -0,0 +1,53 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +// Input Schema for the ExecSandbox tool +const execSandboxInputSchema = `{ + "properties": { + "id": { + "description": "Sandbox identifier.", + "type": "string" + }, + "body": { + "properties": { + "cmd": { + "description": "Program to execute inside the VM, absolute or PATH-resolved.", + "example": "ls", + "type": "string" + }, + "args": { + "description": "Arguments passed directly to the executable without shell parsing.", + "items": { "type": "string" }, + "type": "array" + }, + "stdin": { + "description": "Optional stdin passed to the process.", + "type": "string" + }, + "env": { + "additionalProperties": { "type": "string" }, + "description": "Per-exec environment overrides. Keys must have been declared in the sandbox envs at create time.", + "type": "object" + }, + "stream": { + "description": "Streaming exec is not supported by this MCP tool yet. Leave false or omit.", + "type": "boolean" + } + }, + "required": ["cmd"], + "type": "object" + } + }, + "required": ["id", "body"], + "type": "object" +}` + +// NewExecSandboxMCPTool creates the MCP Tool instance for ExecSandbox +func NewExecSandboxMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "ExecSandbox", + "Run a buffered command inside a sandbox VM.", + []byte(execSandboxInputSchema), + ) +} diff --git a/mcptools/GetDisk.go b/mcptools/GetDisk.go new file mode 100644 index 0000000..ad0a5d6 --- /dev/null +++ b/mcptools/GetDisk.go @@ -0,0 +1,22 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const getDiskInputSchema = `{ + "properties": { + "id_or_name": { + "description": "Disk id, such as disk_, or user-scoped disk name.", + "type": "string" + } + }, + "required": ["id_or_name"], + "type": "object" +}` + +func NewGetDiskMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "GetDisk", + "Get sandbox disk metadata by id or name. Credentials are never returned by the API.", + []byte(getDiskInputSchema), + ) +} diff --git a/mcptools/GetNetwork.go b/mcptools/GetNetwork.go new file mode 100644 index 0000000..6ef2ae0 --- /dev/null +++ b/mcptools/GetNetwork.go @@ -0,0 +1,22 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const getNetworkInputSchema = `{ + "properties": { + "id": { + "description": "Network name or net- id.", + "type": "string" + } + }, + "required": ["id"], + "type": "object" +}` + +func NewGetNetworkMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "GetNetwork", + "Get one sandbox network, including members.", + []byte(getNetworkInputSchema), + ) +} diff --git a/mcptools/ListDisks.go b/mcptools/ListDisks.go new file mode 100644 index 0000000..f53cb0c --- /dev/null +++ b/mcptools/ListDisks.go @@ -0,0 +1,27 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const listDisksInputSchema = `{ + "properties": { + "limit": { + "description": "Maximum number of disks to return.", + "maximum": 500, + "type": "integer" + }, + "offset": { + "description": "Pagination offset.", + "minimum": 0, + "type": "integer" + } + }, + "type": "object" +}` + +func NewListDisksMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "ListDisks", + "List sandbox disks owned by the caller.", + []byte(listDisksInputSchema), + ) +} diff --git a/mcptools/ListNetworks.go b/mcptools/ListNetworks.go new file mode 100644 index 0000000..296ce0e --- /dev/null +++ b/mcptools/ListNetworks.go @@ -0,0 +1,27 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const listNetworksInputSchema = `{ + "properties": { + "limit": { + "description": "Maximum number of networks to return.", + "maximum": 500, + "type": "integer" + }, + "offset": { + "description": "Pagination offset.", + "minimum": 0, + "type": "integer" + } + }, + "type": "object" +}` + +func NewListNetworksMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "ListNetworks", + "List private sandbox networks owned by the caller.", + []byte(listNetworksInputSchema), + ) +} diff --git a/mcptools/ListSandboxDisks.go b/mcptools/ListSandboxDisks.go new file mode 100644 index 0000000..b8d89f7 --- /dev/null +++ b/mcptools/ListSandboxDisks.go @@ -0,0 +1,22 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +const listSandboxDisksInputSchema = `{ + "properties": { + "id": { + "description": "Sandbox identifier.", + "type": "string" + } + }, + "required": ["id"], + "type": "object" +}` + +func NewListSandboxDisksMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "ListSandboxDisks", + "List disks attached to a sandbox, including mount status.", + []byte(listSandboxDisksInputSchema), + ) +} diff --git a/mcptools/UpdateSandbox.go b/mcptools/UpdateSandbox.go new file mode 100644 index 0000000..1df6573 --- /dev/null +++ b/mcptools/UpdateSandbox.go @@ -0,0 +1,44 @@ +package mcptools + +import "github.com/mark3labs/mcp-go/mcp" + +// Input Schema for the UpdateSandbox tool +const updateSandboxInputSchema = `{ + "properties": { + "id": { + "description": "Sandbox identifier.", + "type": "string" + }, + "body": { + "description": "Partial sandbox update. Omitted fields are left unchanged.", + "properties": { + "ingress_enabled": { + "description": "Enable or disable HTTP ingress for this sandbox.", + "type": "boolean" + }, + "auto_pause_after_seconds": { + "description": "Idle timeout in seconds. Valid range is 60 to 86400.", + "maximum": 86400, + "minimum": 60, + "type": ["integer", "null"] + }, + "disable_auto_pause": { + "description": "Set true to clear auto_pause_after_seconds.", + "type": "boolean" + } + }, + "type": "object" + } + }, + "required": ["id", "body"], + "type": "object" +}` + +// NewUpdateSandboxMCPTool creates the MCP Tool instance for UpdateSandbox +func NewUpdateSandboxMCPTool() mcp.Tool { + return mcp.NewToolWithRawSchema( + "UpdateSandbox", + "Partially update sandbox settings such as ingress and auto-pause.", + []byte(updateSandboxInputSchema), + ) +} diff --git a/server.go b/server.go index 3cf5706..dbc3791 100644 --- a/server.go +++ b/server.go @@ -19,6 +19,8 @@ func NewMCPServer() *server.MCPServer { // Register all tools s.AddTool(mcptools.NewAddProjectsToAppMCPTool(), handler.AddProjectsToAppHandler) s.AddTool(mcptools.NewAddServicesToAppMCPTool(), handler.AddServicesToAppHandler) + s.AddTool(mcptools.NewAttachSandboxDiskMCPTool(), handler.AttachSandboxDiskHandler) + s.AddTool(mcptools.NewAttachSandboxNetworkMCPTool(), handler.AttachSandboxNetworkHandler) s.AddTool(mcptools.NewAssignDeploymentToProjectEnvironmentMCPTool(), handler.AssignDeploymentToProjectEnvironmentHandler) s.AddTool(mcptools.NewBuyProjectTemplateMCPTool(), handler.BuyProjectTemplateHandler) s.AddTool(mcptools.NewCancelDeploymentMCPTool(), handler.CancelDeploymentHandler) @@ -29,26 +31,37 @@ func NewMCPServer() *server.MCPServer { s.AddTool(mcptools.NewCreateAppMCPTool(), handler.CreateAppHandler) s.AddTool(mcptools.NewCreateCronjobMCPTool(), handler.CreateCronjobHandler) s.AddTool(mcptools.NewCreateDeploymentMCPTool(), handler.CreateDeploymentHandler) + s.AddTool(mcptools.NewCreateDiskMCPTool(), handler.CreateDiskHandler) s.AddTool(mcptools.NewCreateDomainMCPTool(), handler.CreateDomainHandler) + s.AddTool(mcptools.NewCreateNetworkMCPTool(), handler.CreateNetworkHandler) s.AddTool(mcptools.NewCreateProjectMCPTool(), handler.CreateProjectHandler) s.AddTool(mcptools.NewCreateProjectEnvironmentMCPTool(), handler.CreateProjectEnvironmentHandler) s.AddTool(mcptools.NewCreateProjectTemplateMCPTool(), handler.CreateProjectTemplateHandler) + s.AddTool(mcptools.NewCreateSandboxMCPTool(), handler.CreateSandboxHandler) s.AddTool(mcptools.NewDeleteAppMCPTool(), handler.DeleteAppHandler) s.AddTool(mcptools.NewDeleteCronjobMCPTool(), handler.DeleteCronjobHandler) s.AddTool(mcptools.NewDeleteDeploymentMCPTool(), handler.DeleteDeploymentHandler) + s.AddTool(mcptools.NewDeleteDiskMCPTool(), handler.DeleteDiskHandler) s.AddTool(mcptools.NewDeleteDomainMCPTool(), handler.DeleteDomainHandler) + s.AddTool(mcptools.NewDeleteNetworkMCPTool(), handler.DeleteNetworkHandler) s.AddTool(mcptools.NewDeleteProjectMCPTool(), handler.DeleteProjectHandler) s.AddTool(mcptools.NewDeleteProjectEnvironmentMCPTool(), handler.DeleteProjectEnvironmentHandler) s.AddTool(mcptools.NewDeleteProjectTemplateMCPTool(), handler.DeleteProjectTemplateHandler) + s.AddTool(mcptools.NewDeleteSandboxMCPTool(), handler.DeleteSandboxHandler) + s.AddTool(mcptools.NewDetachSandboxDiskMCPTool(), handler.DetachSandboxDiskHandler) + s.AddTool(mcptools.NewDetachSandboxNetworkMCPTool(), handler.DetachSandboxNetworkHandler) s.AddTool(mcptools.NewDeployProjectTemplateViaGithubMCPTool(), handler.DeployProjectTemplateViaGithubHandler) s.AddTool(mcptools.NewDownloadDeploymentMCPTool(), handler.DownloadDeploymentHandler) s.AddTool(mcptools.NewDownloadProjectTemplateMCPTool(), handler.DownloadProjectTemplateHandler) + s.AddTool(mcptools.NewExecSandboxMCPTool(), handler.ExecSandboxHandler) s.AddTool(mcptools.NewGetBuildLogsMCPTool(), handler.GetBuildLogsHandler) s.AddTool(mcptools.NewGetCronjobMCPTool(), handler.GetCronjobHandler) s.AddTool(mcptools.NewGetCurrentUserMCPTool(), handler.GetCurrentUserHandler) s.AddTool(mcptools.NewGetDeploymentMCPTool(), handler.GetDeploymentHandler) s.AddTool(mcptools.NewGetDeploymentLogsMCPTool(), handler.GetDeploymentLogsHandler) + s.AddTool(mcptools.NewGetDiskMCPTool(), handler.GetDiskHandler) s.AddTool(mcptools.NewGetGithubRepositoryContentMCPTool(), handler.GetGithubRepositoryContentHandler) + s.AddTool(mcptools.NewGetNetworkMCPTool(), handler.GetNetworkHandler) s.AddTool(mcptools.NewGetProjectMCPTool(), handler.GetProjectHandler) s.AddTool(mcptools.NewGetProjectEnvironmentLogsMCPTool(), handler.GetProjectEnvironmentLogsHandler) s.AddTool(mcptools.NewGetProjectTemplateMCPTool(), handler.GetProjectTemplateHandler) @@ -64,10 +77,12 @@ func NewMCPServer() *server.MCPServer { s.AddTool(mcptools.NewListCronjobActivitiesMCPTool(), handler.ListCronjobActivitiesHandler) s.AddTool(mcptools.NewListCronjobsMCPTool(), handler.ListCronjobsHandler) s.AddTool(mcptools.NewListDeploymentsMCPTool(), handler.ListDeploymentsHandler) + s.AddTool(mcptools.NewListDisksMCPTool(), handler.ListDisksHandler) s.AddTool(mcptools.NewListDomainsMCPTool(), handler.ListDomainsHandler) s.AddTool(mcptools.NewListGithubRepositoriesMCPTool(), handler.ListGithubRepositoriesHandler) s.AddTool(mcptools.NewListGithubRepositoryBranchesMCPTool(), handler.ListGithubRepositoryBranchesHandler) s.AddTool(mcptools.NewListMyProjectTemplatesMCPTool(), handler.ListMyProjectTemplatesHandler) + s.AddTool(mcptools.NewListNetworksMCPTool(), handler.ListNetworksHandler) s.AddTool(mcptools.NewListProjectEnvironmentsMCPTool(), handler.ListProjectEnvironmentsHandler) s.AddTool(mcptools.NewListProjectTemplateCountsMCPTool(), handler.ListProjectTemplateCountsHandler) s.AddTool(mcptools.NewListProjectTemplatePurchasesMCPTool(), handler.ListProjectTemplatePurchasesHandler) @@ -76,6 +91,7 @@ func NewMCPServer() *server.MCPServer { s.AddTool(mcptools.NewListProjectsMCPTool(), handler.ListProjectsHandler) s.AddTool(mcptools.NewListProjectsByAppMCPTool(), handler.ListProjectsByAppHandler) s.AddTool(mcptools.NewListPublishedProjectTemplatesMCPTool(), handler.ListPublishedProjectTemplatesHandler) + s.AddTool(mcptools.NewListSandboxDisksMCPTool(), handler.ListSandboxDisksHandler) s.AddTool(mcptools.NewListServicesByAppMCPTool(), handler.ListServicesByAppHandler) s.AddTool(mcptools.NewRefreshDomainMCPTool(), handler.RefreshDomainHandler) s.AddTool(mcptools.NewRemoveProjectsFromAppMCPTool(), handler.RemoveProjectsFromAppHandler) @@ -99,6 +115,7 @@ func NewMCPServer() *server.MCPServer { s.AddTool(mcptools.NewUpdateProjectSettingsMCPTool(), handler.UpdateProjectSettingsHandler) s.AddTool(mcptools.NewUpdateProjectTemplateMCPTool(), handler.UpdateProjectTemplateHandler) s.AddTool(mcptools.NewUpdateProjectTemplateStatusMCPTool(), handler.UpdateProjectTemplateStatusHandler) + s.AddTool(mcptools.NewUpdateSandboxMCPTool(), handler.UpdateSandboxHandler) s.AddTool(mcptools.NewUploadDeploymentBase64FilesMCPTool(), handler.UploadDeploymentBase64FilesHandler) s.AddTool(mcptools.NewUploadDeploymentFilesMCPTool(), handler.UploadDeploymentFilesHandler) s.AddTool(mcptools.NewUploadDeploymentZipMCPTool(), handler.UploadDeploymentZipHandler)