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
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user