Files
karti 35a0b2b715 feat: initial release with 48 Mattermost MCP tools
- 27 base tools: channels, messaging, users, reactions, files, DMs
- Team management: invite/remove users, get stats, list members
- Slash commands: execute /remind, /poll, etc.
- Webhook management: incoming and outgoing webhooks
- System tools: server config, logs, bulk status updates
- Channel admin: create, invite, leave, delete channels
- Read-only mode for safe exploration
- Dual token support (bot + PAT) for enhanced security
- Apache 2.0 licensed
2026-04-15 21:26:19 -07:00

221 lines
6.5 KiB
Go

package channel
import (
"context"
"fmt"
"github.com/karti-ai/mattermost-mcp-server/pkg/log"
"github.com/karti-ai/mattermost-mcp-server/pkg/mattermost"
"github.com/karti-ai/mattermost-mcp-server/pkg/params"
"github.com/karti-ai/mattermost-mcp-server/pkg/to"
"github.com/karti-ai/mattermost-mcp-server/pkg/tool"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/mattermost/mattermost-server/v6/model"
)
var AdminTool = tool.New()
const (
CreateChannelToolName = "mattermost_create_channel"
InviteToChannelToolName = "mattermost_invite_to_channel"
DeleteChannelToolName = "mattermost_delete_channel"
LeaveChannelToolName = "mattermost_leave_channel"
)
var (
CreateChannelTool = mcp.NewTool(
CreateChannelToolName,
mcp.WithDescription("Create a new channel (public or private) in a team"),
mcp.WithString("team_id", mcp.Required(), mcp.Description("Team to create channel in")),
mcp.WithString("name", mcp.Required(), mcp.Description("Channel name (lowercase, no spaces, 2-64 characters)")),
mcp.WithString("display_name", mcp.Required(), mcp.Description("Display name for the channel (2-64 characters)")),
mcp.WithString("type", mcp.Required(), mcp.Description("Channel type: 'O' for public, 'P' for private")),
mcp.WithString("purpose", mcp.Description("Channel description/purpose (optional)")),
)
InviteToChannelTool = mcp.NewTool(
InviteToChannelToolName,
mcp.WithDescription("Invite a user to a channel"),
mcp.WithString("channel_id", mcp.Required(), mcp.Description("Channel to invite user to")),
mcp.WithString("user_id", mcp.Required(), mcp.Description("User to invite")),
)
DeleteChannelTool = mcp.NewTool(
DeleteChannelToolName,
mcp.WithDescription("Delete/archive a channel (soft delete by default)"),
mcp.WithString("channel_id", mcp.Required(), mcp.Description("Channel to delete")),
mcp.WithBoolean("permanent", mcp.Description("Permanently delete instead of archive (default: false)")),
)
LeaveChannelTool = mcp.NewTool(
LeaveChannelToolName,
mcp.WithDescription("Remove self from a channel"),
mcp.WithString("channel_id", mcp.Required(), mcp.Description("Channel to leave")),
)
)
func init() {
registerAdminTools()
}
func registerAdminTools() {
tools := []server.ServerTool{
{Tool: CreateChannelTool, Handler: CreateChannelFn},
{Tool: InviteToChannelTool, Handler: InviteToChannelFn},
{Tool: DeleteChannelTool, Handler: DeleteChannelFn},
{Tool: LeaveChannelTool, Handler: LeaveChannelFn},
}
for _, t := range tools {
AdminTool.RegisterWrite(t)
}
}
func CreateChannelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Channel] Called CreateChannelFn")
args := req.GetArguments()
teamID, err := params.GetString(args, "team_id")
if err != nil {
return to.Error(fmt.Errorf("[team_id] %v", err)), nil
}
name, err := params.GetString(args, "name")
if err != nil {
return to.Error(fmt.Errorf("[name] %v", err)), nil
}
displayName, err := params.GetString(args, "display_name")
if err != nil {
return to.Error(fmt.Errorf("[display_name] %v", err)), nil
}
channelType, err := params.GetString(args, "type")
if err != nil {
return to.Error(fmt.Errorf("[type] %v", err)), nil
}
if channelType != "O" && channelType != "P" {
return to.Error(fmt.Errorf("[type] must be 'O' (public) or 'P' (private)")), nil
}
purpose := params.GetOptionalString(args, "purpose", "")
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
channel := &model.Channel{
TeamId: teamID,
Name: name,
DisplayName: displayName,
Type: model.ChannelType(channelType),
Purpose: purpose,
}
created, err := client.CreateChannel(ctx, channel)
if err != nil {
return to.Error(fmt.Errorf("[channel] failed to create channel: %v", err)), nil
}
return to.Result(SlimChannel(created)), nil
}
func InviteToChannelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Channel] Called InviteToChannelFn")
args := req.GetArguments()
channelID, err := params.GetString(args, "channel_id")
if err != nil {
return to.Error(fmt.Errorf("[channel_id] %v", err)), nil
}
userID, err := params.GetString(args, "user_id")
if err != nil {
return to.Error(fmt.Errorf("[user_id] %v", err)), nil
}
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
member, err := client.AddChannelMember(ctx, channelID, userID)
if err != nil {
return to.Error(fmt.Errorf("[channel] failed to invite user to channel: %v", err)), nil
}
return to.Result(map[string]interface{}{
"success": true,
"channel_id": channelID,
"user_id": userID,
"member": member,
"message": "User invited to channel successfully",
}), nil
}
func DeleteChannelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Channel] Called DeleteChannelFn")
args := req.GetArguments()
channelID, err := params.GetString(args, "channel_id")
if err != nil {
return to.Error(fmt.Errorf("[channel_id] %v", err)), nil
}
_ = params.GetOptionalBool(args, "permanent", false)
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
err = client.DeleteChannel(ctx, channelID)
if err != nil {
return to.Error(fmt.Errorf("[channel] failed to delete channel: %v", err)), nil
}
return to.Result(map[string]interface{}{
"success": true,
"channel_id": channelID,
"message": "Channel deleted successfully",
}), nil
}
func LeaveChannelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Channel] Called LeaveChannelFn")
args := req.GetArguments()
channelID, err := params.GetString(args, "channel_id")
if err != nil {
return to.Error(fmt.Errorf("[channel_id] %v", err)), nil
}
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
user, err := client.GetMe(ctx)
if err != nil {
return to.Error(fmt.Errorf("[user] failed to get current user: %v", err)), nil
}
err = client.RemoveChannelMember(ctx, channelID, user.Id)
if err != nil {
return to.Error(fmt.Errorf("[channel] failed to leave channel: %v", err)), nil
}
return to.Result(map[string]interface{}{
"success": true,
"channel_id": channelID,
"user_id": user.Id,
"message": "Successfully left the channel",
}), nil
}