Files
mattermost-mcp-server/operation/messaging/messaging.go
T
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

370 lines
10 KiB
Go

package messaging
import (
"context"
"fmt"
"strings"
"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 Tool = tool.New()
const (
SendMessageToolName = "mattermost_send_message"
EditMessageToolName = "mattermost_edit_message"
DeleteMessageToolName = "mattermost_delete_message"
BulkDeleteMessagesToolName = "mattermost_bulk_delete_messages"
PinPostToolName = "mattermost_pin_post"
UnpinPostToolName = "mattermost_unpin_post"
GetPinnedPostsToolName = "mattermost_get_pinned_posts"
GetPostToolName = "mattermost_get_post"
)
var (
SendMessageTool = mcp.NewTool(
SendMessageToolName,
mcp.WithDescription("Send a message to a Mattermost channel"),
mcp.WithString("channel_id", mcp.Required(), mcp.Description("Channel ID to send the message to")),
mcp.WithString("message", mcp.Required(), mcp.Description("Message content to send")),
mcp.WithString("thread_id", mcp.Description("Thread ID for replies (optional). If provided, message will be posted as a reply in the thread")),
)
EditMessageTool = mcp.NewTool(
EditMessageToolName,
mcp.WithDescription("Edit an existing message in Mattermost"),
mcp.WithString("post_id", mcp.Required(), mcp.Description("Post ID of the message to edit")),
mcp.WithString("message", mcp.Required(), mcp.Description("New message content")),
)
DeleteMessageTool = mcp.NewTool(
DeleteMessageToolName,
mcp.WithDescription("Delete a message from Mattermost"),
mcp.WithString("post_id", mcp.Required(), mcp.Description("Post ID of the message to delete")),
)
BulkDeleteMessagesTool = mcp.NewTool(
BulkDeleteMessagesToolName,
mcp.WithDescription("Delete multiple messages at once (up to 100)"),
mcp.WithString("post_ids", mcp.Required(), mcp.Description("Comma-separated list of post IDs to delete (max 100)")),
)
PinPostTool = mcp.NewTool(
PinPostToolName,
mcp.WithDescription("Pin a post to a channel"),
mcp.WithString("post_id", mcp.Required(), mcp.Description("Post ID to pin")),
)
UnpinPostTool = mcp.NewTool(
UnpinPostToolName,
mcp.WithDescription("Unpin a post from a channel"),
mcp.WithString("post_id", mcp.Required(), mcp.Description("Post ID to unpin")),
)
GetPinnedPostsTool = mcp.NewTool(
GetPinnedPostsToolName,
mcp.WithDescription("Get all pinned posts in a channel"),
mcp.WithString("channel_id", mcp.Required(), mcp.Description("Channel ID to get pinned posts from")),
)
GetPostTool = mcp.NewTool(
GetPostToolName,
mcp.WithDescription("Get a single post by its ID"),
mcp.WithString("post_id", mcp.Required(), mcp.Description("Post ID to retrieve")),
)
)
func init() {
registerTools()
}
func registerTools() {
tools := []server.ServerTool{
{Tool: SendMessageTool, Handler: SendMessageFn},
{Tool: EditMessageTool, Handler: EditMessageFn},
{Tool: DeleteMessageTool, Handler: DeleteMessageFn},
{Tool: BulkDeleteMessagesTool, Handler: BulkDeleteMessagesFn},
{Tool: PinPostTool, Handler: PinPostFn},
{Tool: UnpinPostTool, Handler: UnpinPostFn},
{Tool: GetPinnedPostsTool, Handler: GetPinnedPostsFn},
{Tool: GetPostTool, Handler: GetPostFn},
}
for _, t := range tools {
if t.Tool.Name == GetPinnedPostsToolName || t.Tool.Name == GetPostToolName {
Tool.RegisterRead(t)
} else {
Tool.RegisterWrite(t)
}
}
}
func SendMessageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Messaging] Called SendMessageFn")
args := req.GetArguments()
channelId, err := params.GetString(args, "channel_id")
if err != nil {
return to.Error(fmt.Errorf("[channel_id] %v", err)), nil
}
message, err := params.GetString(args, "message")
if err != nil {
return to.Error(fmt.Errorf("[message] %v", err)), nil
}
threadId := params.GetOptionalString(args, "thread_id", "")
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
post := &model.Post{
ChannelId: channelId,
Message: message,
}
if threadId != "" {
post.RootId = threadId
}
result, err := client.CreatePost(ctx, post)
if err != nil {
return to.Error(fmt.Errorf("[post] failed to send message: %v", err)), nil
}
return to.Result(SlimPost(result)), nil
}
func EditMessageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Messaging] Called EditMessageFn")
args := req.GetArguments()
postId, err := params.GetString(args, "post_id")
if err != nil {
return to.Error(fmt.Errorf("[post_id] %v", err)), nil
}
message, err := params.GetString(args, "message")
if err != nil {
return to.Error(fmt.Errorf("[message] %v", err)), nil
}
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
post := &model.Post{
Message: message,
}
result, err := client.UpdatePost(ctx, postId, post)
if err != nil {
return to.Error(fmt.Errorf("[post] failed to edit message: %v", err)), nil
}
return to.Result(SlimPost(result)), nil
}
func DeleteMessageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Messaging] Called DeleteMessageFn")
args := req.GetArguments()
postId, err := params.GetString(args, "post_id")
if err != nil {
return to.Error(fmt.Errorf("[post_id] %v", err)), nil
}
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
err = client.DeletePost(ctx, postId)
if err != nil {
return to.Error(fmt.Errorf("[post] failed to delete message: %v", err)), nil
}
return to.Result(map[string]interface{}{
"success": true,
"post_id": postId,
"message": "Message deleted successfully",
}), nil
}
func BulkDeleteMessagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Messaging] Called BulkDeleteMessagesFn")
args := req.GetArguments()
postIDsStr, err := params.GetString(args, "post_ids")
if err != nil {
return to.Error(fmt.Errorf("[post_ids] %v", err)), nil
}
postIDs := strings.Split(postIDsStr, ",")
if len(postIDs) > 100 {
return to.Error(fmt.Errorf("[post_ids] too many post IDs (max 100, got %d)", len(postIDs))), nil
}
for i, id := range postIDs {
postIDs[i] = strings.TrimSpace(id)
}
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
deleted := 0
failed := 0
var failedIDs []string
var lastError string
for _, postID := range postIDs {
err := client.DeletePost(ctx, postID)
if err != nil {
failed++
failedIDs = append(failedIDs, postID)
lastError = err.Error()
} else {
deleted++
}
}
result := map[string]interface{}{
"total": len(postIDs),
"deleted": deleted,
"failed": failed,
"successful": deleted == len(postIDs),
}
if failed > 0 {
result["failed_ids"] = failedIDs
result["last_error"] = lastError
return to.Result(result), nil
}
return to.Result(result), nil
}
func PinPostFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Messaging] Called PinPostFn")
args := req.GetArguments()
postId, err := params.GetString(args, "post_id")
if err != nil {
return to.Error(fmt.Errorf("[post_id] %v", err)), nil
}
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
err = client.PinPost(ctx, postId)
if err != nil {
return to.Error(fmt.Errorf("[post] failed to pin: %v", err)), nil
}
return to.Result(map[string]interface{}{
"success": true,
"post_id": postId,
"message": "Post pinned successfully",
}), nil
}
func UnpinPostFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Messaging] Called UnpinPostFn")
args := req.GetArguments()
postId, err := params.GetString(args, "post_id")
if err != nil {
return to.Error(fmt.Errorf("[post_id] %v", err)), nil
}
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
err = client.UnpinPost(ctx, postId)
if err != nil {
return to.Error(fmt.Errorf("[post] failed to unpin: %v", err)), nil
}
return to.Result(map[string]interface{}{
"success": true,
"post_id": postId,
"message": "Post unpinned successfully",
}), nil
}
func GetPinnedPostsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Messaging] Called GetPinnedPostsFn")
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
}
posts, err := client.GetPinnedPosts(ctx, channelId)
if err != nil {
return to.Error(fmt.Errorf("[channel] failed to get pinned posts: %v", err)), nil
}
results := make([]map[string]interface{}, 0, len(posts.Posts))
for _, post := range posts.Posts {
results = append(results, SlimPost(post))
}
return to.Result(map[string]interface{}{
"posts": results,
"count": len(results),
"channel_id": channelId,
}), nil
}
func GetPostFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[Messaging] Called GetPostFn")
args := req.GetArguments()
postId, err := params.GetString(args, "post_id")
if err != nil {
return to.Error(fmt.Errorf("[post_id] %v", err)), nil
}
client := mattermost.GetGlobalClient()
if client == nil {
return to.Error(fmt.Errorf("[internal] client not initialized")), nil
}
post, err := client.GetPost(ctx, postId)
if err != nil {
return to.Error(fmt.Errorf("[post] failed to get: %v", err)), nil
}
return to.Result(SlimPost(post)), nil
}