internal/mcptools/manage_channels.go
Rama main · réplica de lectura del repositorio original.
package mcptools
import (
"context"
"errors"
"log"
"strconv"
mcpprot "github.com/modelcontextprotocol/go-sdk/mcp"
)
var (
channelCreateAccess = Access{Perm: "channel:create", Fallback: FallbackAdminOnly}
channelDeleteAccess = Access{Perm: "channel:delete", Fallback: FallbackAdminOnly}
)
type CreateChannelInput struct {
AppId string `json:"appId" jsonschema:"the app id, as returned by get_apps"`
Channel string `json:"channel" jsonschema:"the release channel name to create"`
Branch string `json:"branch,omitempty" jsonschema:"optional branch name to point the channel at, as returned by get_branches; the channel is created unmapped when omitted"`
}
type CreateChannelOutput struct {
ChannelId string `json:"channelId"`
Channel string `json:"channel"`
Branch string `json:"branch,omitempty"`
}
func createChannelHandler(deps Deps) func(ctx context.Context, req *mcpprot.CallToolRequest, input CreateChannelInput) (*mcpprot.CallToolResult, CreateChannelOutput, error) {
return func(ctx context.Context, req *mcpprot.CallToolRequest, input CreateChannelInput) (*mcpprot.CallToolResult, CreateChannelOutput, error) {
ctx, principal, err := requireAppPermission(ctx, deps, req, input.AppId, channelCreateAccess)
if err != nil {
return nil, CreateChannelOutput{}, err
}
if input.Channel == "" {
return nil, CreateChannelOutput{}, errors.New("channel is required")
}
var branchName *string
if input.Branch != "" {
branchName = &input.Branch
}
channelId, err := deps.ChannelWriter.CreateChannel(ctx, input.AppId, branchName, input.Channel)
if err != nil {
return nil, CreateChannelOutput{}, writeError(err, "create the channel", "mcp create_channel", principal, input.AppId)
}
// Ids travel as strings: an int64 as a JSON number corrupts past 2^53.
return nil, CreateChannelOutput{
ChannelId: strconv.FormatInt(channelId, 10),
Channel: input.Channel,
Branch: input.Branch,
}, nil
}
}
func registerCreateChannel(server *mcpprot.Server, deps Deps) {
mcpprot.AddTool(server, &mcpprot.Tool{
Name: "create_channel",
Description: "Create a release channel on an app (appId and channel required), optionally pointing it at a branch. Requires the channel:create permission.",
Annotations: &mcpprot.ToolAnnotations{
Title: "Create channel",
DestructiveHint: boolPtr(false),
IdempotentHint: false,
},
}, createChannelHandler(deps))
}
type DeleteChannelInput struct {
AppId string `json:"appId" jsonschema:"the app id, as returned by get_apps"`
Channel string `json:"channel" jsonschema:"the release channel name to delete, as returned by get_channels"`
}
type DeleteChannelOutput struct {
Deleted bool `json:"deleted"`
Channel string `json:"channel"`
}
func deleteChannelHandler(deps Deps) func(ctx context.Context, req *mcpprot.CallToolRequest, input DeleteChannelInput) (*mcpprot.CallToolResult, DeleteChannelOutput, error) {
return func(ctx context.Context, req *mcpprot.CallToolRequest, input DeleteChannelInput) (*mcpprot.CallToolResult, DeleteChannelOutput, error) {
ctx, principal, err := requireAppPermission(ctx, deps, req, input.AppId, channelDeleteAccess)
if err != nil {
return nil, DeleteChannelOutput{}, err
}
if input.Channel == "" {
return nil, DeleteChannelOutput{}, errors.New("channel is required; list the channels with get_channels")
}
if err := deps.ChannelWriter.DeleteChannel(ctx, input.Channel, input.AppId); err != nil {
return nil, DeleteChannelOutput{}, writeError(err, "delete the channel", "mcp delete_channel", principal, input.AppId)
}
log.Printf("mcp delete_channel: user %s deleted channel %s of app %s", principal.UserId, input.Channel, input.AppId)
return nil, DeleteChannelOutput{Deleted: true, Channel: input.Channel}, nil
}
}
func registerDeleteChannel(server *mcpprot.Server, deps Deps) {
mcpprot.AddTool(server, &mcpprot.Tool{
Name: "delete_channel",
Description: "Delete a release channel of an app (appId and channel required). Devices still pointing at this channel stop receiving updates. Requires the channel:delete permission.",
Annotations: &mcpprot.ToolAnnotations{
Title: "Delete channel",
DestructiveHint: boolPtr(true),
IdempotentHint: false,
},
}, deleteChannelHandler(deps))
}