mirror of
https://github.com/paymog/slack-cli.git
synced 2026-09-18 23:43:49 +08:00
feat: add blocks parameter and bump slack-go-util to v0.4.0
Add optional `blocks` parameter to conversations_add_message for raw Slack Block Kit JSON support (rich_text lists, code blocks, etc.). When blocks is provided it takes precedence over text/content_type for message rendering. The text parameter serves as notification fallback. The blocks argument accepts both a JSON string and a raw JSON array to accommodate different MCP client serialization behaviors. Also bumps takara2314/slack-go-util from v0.3.0 to v0.4.0 which adds nested list support to the existing text/markdown conversion path.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
module github.com/korotovsky/slack-mcp-server
|
||||
|
||||
go 1.24.4
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
|
||||
@@ -13,9 +13,9 @@ require (
|
||||
github.com/rusq/slackauth v0.7.1
|
||||
github.com/rusq/slackdump/v3 v3.1.13
|
||||
github.com/rusq/tagops v0.1.1
|
||||
github.com/slack-go/slack v0.17.3
|
||||
github.com/slack-go/slack v0.19.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/takara2314/slack-go-util v0.3.0
|
||||
github.com/takara2314/slack-go-util v0.4.0
|
||||
go.uber.org/zap v1.27.1
|
||||
golang.ngrok.com/ngrok/v2 v2.1.1
|
||||
golang.org/x/net v0.50.0
|
||||
@@ -87,7 +87,7 @@ require (
|
||||
github.com/ysmood/got v0.42.3 // indirect
|
||||
github.com/ysmood/gson v0.7.3 // indirect
|
||||
github.com/ysmood/leakless v0.9.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.13 // indirect
|
||||
github.com/yuin/goldmark v1.7.16 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.ngrok.com/muxado/v2 v2.0.1 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
|
||||
@@ -153,6 +153,8 @@ github.com/rusq/tagops v0.1.1 h1:R5MHPR822lSg3LFr0RS3DFS0CapRiqtuHVD5NlOMOvY=
|
||||
github.com/rusq/tagops v0.1.1/go.mod h1:mUJ5WoHxrSv9wreCrHQkAeMevt5aXFadlOdLM6UsoHc=
|
||||
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
|
||||
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
|
||||
github.com/slack-go/slack v0.19.0 h1:J8lL/nGTsIUX53HU8YxZeI3PDkA+sxZsFrI2Dew7h44=
|
||||
github.com/slack-go/slack v0.19.0/go.mod h1:K81UmCivcYd/5Jmz8vLBfuyoZ3B4rQC2GHVXHteXiAE=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -162,6 +164,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/takara2314/slack-go-util v0.3.0 h1:vA4WV5liJkZ9JMa2dVN+Rj6u8EW2jRiupOGkn52SMrg=
|
||||
github.com/takara2314/slack-go-util v0.3.0/go.mod h1:zAMjTWVT2/cDkJtbFm+AtNg5dp+l0FpknJZs8q95NWs=
|
||||
github.com/takara2314/slack-go-util v0.4.0 h1:sv/Cd95HNvIPDejpTY1bytT68Lb4dXgtcUFQhJZJNis=
|
||||
github.com/takara2314/slack-go-util v0.4.0/go.mod h1:6m63Q+4p5F1qfa5NKfqspSWkgKCM168ZjabHBTXqQj0=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
@@ -197,6 +201,8 @@ github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
@@ -96,6 +97,7 @@ type addMessageParams struct {
|
||||
threadTs string
|
||||
text string
|
||||
contentType string
|
||||
blocks []slack.Block
|
||||
}
|
||||
|
||||
type addReactionParams struct {
|
||||
@@ -221,21 +223,30 @@ func (ch *ConversationsHandler) ConversationsAddMessageHandler(ctx context.Conte
|
||||
options = append(options, slack.MsgOptionTS(params.threadTs))
|
||||
}
|
||||
|
||||
switch params.contentType {
|
||||
case "text/plain":
|
||||
options = append(options, slack.MsgOptionDisableMarkdown())
|
||||
options = append(options, slack.MsgOptionText(params.text, false))
|
||||
case "text/markdown":
|
||||
blocks, err := slackGoUtil.ConvertMarkdownTextToBlocks(params.text)
|
||||
if err != nil {
|
||||
ch.logger.Warn("Markdown parsing error", zap.Error(err))
|
||||
if params.blocks != nil {
|
||||
// Raw blocks provided: use them directly. If text is also provided, it
|
||||
// serves as the notification/fallback text.
|
||||
options = append(options, slack.MsgOptionBlocks(params.blocks...))
|
||||
if params.text != "" {
|
||||
options = append(options, slack.MsgOptionText(params.text, false))
|
||||
}
|
||||
} else {
|
||||
switch params.contentType {
|
||||
case "text/plain":
|
||||
options = append(options, slack.MsgOptionDisableMarkdown())
|
||||
options = append(options, slack.MsgOptionText(params.text, false))
|
||||
} else {
|
||||
options = append(options, slack.MsgOptionBlocks(blocks...))
|
||||
case "text/markdown":
|
||||
blocks, err := slackGoUtil.ConvertMarkdownTextToBlocks(params.text)
|
||||
if err != nil {
|
||||
ch.logger.Warn("Markdown parsing error", zap.Error(err))
|
||||
options = append(options, slack.MsgOptionDisableMarkdown())
|
||||
options = append(options, slack.MsgOptionText(params.text, false))
|
||||
} else {
|
||||
options = append(options, slack.MsgOptionBlocks(blocks...))
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("content_type must be either 'text/plain' or 'text/markdown'")
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("content_type must be either 'text/plain' or 'text/markdown'")
|
||||
}
|
||||
|
||||
unfurlOpt := os.Getenv("SLACK_MCP_ADD_MESSAGE_UNFURLING")
|
||||
@@ -1698,10 +1709,6 @@ func (ch *ConversationsHandler) parseParamsToolAddMessage(ctx context.Context, r
|
||||
// Backward compatibility with "payload" parameter
|
||||
msgText = request.GetString("payload", "")
|
||||
}
|
||||
if msgText == "" {
|
||||
ch.logger.Error("Message text missing")
|
||||
return nil, errors.New("text must be a string")
|
||||
}
|
||||
|
||||
contentType := request.GetString("content_type", "text/markdown")
|
||||
if contentType != "text/plain" && contentType != "text/markdown" {
|
||||
@@ -1709,11 +1716,49 @@ func (ch *ConversationsHandler) parseParamsToolAddMessage(ctx context.Context, r
|
||||
return nil, errors.New("content_type must be either 'text/plain' or 'text/markdown'")
|
||||
}
|
||||
|
||||
// Parse optional raw blocks JSON. Accepts blocks as either:
|
||||
// - A JSON string containing a blocks array: "blocks": "[{...}]"
|
||||
// - A raw JSON array (parsed by MCP SDK): "blocks": [{...}]
|
||||
var blocks []slack.Block
|
||||
args := request.GetArguments()
|
||||
if rawBlocks, ok := args["blocks"]; ok && rawBlocks != nil {
|
||||
var blocksJSON []byte
|
||||
switch v := rawBlocks.(type) {
|
||||
case string:
|
||||
if v != "" {
|
||||
blocksJSON = []byte(v)
|
||||
}
|
||||
default:
|
||||
// Raw JSON array/object passed directly - re-marshal to bytes
|
||||
var err error
|
||||
blocksJSON, err = json.Marshal(v)
|
||||
if err != nil {
|
||||
ch.logger.Error("Failed to marshal blocks argument", zap.Error(err))
|
||||
return nil, fmt.Errorf("blocks must be valid Slack Block Kit JSON: %w", err)
|
||||
}
|
||||
}
|
||||
if blocksJSON != nil {
|
||||
var slackBlocks slack.Blocks
|
||||
if err := json.Unmarshal(blocksJSON, &slackBlocks); err != nil {
|
||||
ch.logger.Error("Failed to parse blocks JSON", zap.Error(err))
|
||||
return nil, fmt.Errorf("blocks must be valid Slack Block Kit JSON: %w", err)
|
||||
}
|
||||
blocks = slackBlocks.BlockSet
|
||||
}
|
||||
}
|
||||
|
||||
// Require either text or blocks
|
||||
if msgText == "" && blocks == nil {
|
||||
ch.logger.Error("Message text and blocks both missing")
|
||||
return nil, errors.New("either text or blocks must be provided")
|
||||
}
|
||||
|
||||
return &addMessageParams{
|
||||
channel: channel,
|
||||
threadTs: threadTs,
|
||||
text: msgText,
|
||||
contentType: contentType,
|
||||
blocks: blocks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,10 @@ func NewMCPServer(provider *provider.ApiProvider, logger *zap.Logger, enabledToo
|
||||
),
|
||||
mcp.WithString("content_type",
|
||||
mcp.DefaultString("text/markdown"),
|
||||
mcp.Description("Content type of the message. Default is 'text/markdown'. Allowed values: 'text/markdown', 'text/plain'."),
|
||||
mcp.Description("Content type of the message. Default is 'text/markdown'. Allowed values: 'text/markdown', 'text/plain'. Ignored when blocks is provided."),
|
||||
),
|
||||
mcp.WithString("blocks",
|
||||
mcp.Description("Raw Slack Block Kit JSON array for rich message formatting (rich_text lists, code blocks, etc.). When provided, this takes precedence over text/content_type for rendering. The text parameter becomes the notification fallback text."),
|
||||
),
|
||||
), conversationsHandler.ConversationsAddMessageHandler)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user