Skip to content

feat: add native Slack message forwarding - #7

Merged
upsetbit merged 1 commit into
masterfrom
feature/native-message-forward
Aug 31, 2026
Merged

upsetbit merged 1 commit into
masterfrom
feature/native-message-forward

Conversation

@upsetbit

Copy link
Copy Markdown
Contributor

What

  • Add conversations_forward_message, a browser-session-only MCP write tool for native Slack message forwarding.
  • Accept either a Slack message permalink or an explicit source channel plus timestamp, along with a required destination channel and optional comment.
  • Parse permalinks locally and render comments as rich-text wrapper blocks, including structured Slack mentions.
  • Guard the tool behind SLACK_MCP_FORWARD_MESSAGE_TOOL destination-channel policies or explicit SLACK_MCP_ENABLED_TOOLS registration.
  • Document the tool, schema, authentication requirement, and configuration in the README and usage guide.

Why and mechanism

A native forward is not a chat.postMessage variant. This change calls the internal web-client endpoint used by Slack itself:

POST https://<workspace>.slack.com/api/chat.shareMessage
channel=<source_channel_id>
timestamp=<source_message_ts>
share_channel=<destination_channel_id>
blocks=<optional comment-only rich_text blocks>
token=<xoxc session token>
Cookie: d=<xoxd session cookie>

Slack constructs the embedded share attachment server-side from channel and timestamp. The source permalink is used only to derive those two values and is never copied into the outer message text.

The implementation deliberately rejects the two lookalikes that do not produce a true native forward:

  • chat.postMessage with attachments: [{from_url: ...}]: Slack strips the synthetic attachment, yielding zero attachments.
  • Posting the permalink in text with unfurl_links=true: Slack may asynchronously unfurl it, but the raw permalink remains visible in the message.

Readback proof

The verified readback shape for a message created through chat.shareMessage is:

{
  "text": "<optional comment only; no http substring>",
  "attachments": [
    {
      "is_msg_unfurl": true,
      "is_share": true,
      "author_name": "<original author>",
      "from_url": "<source message URL>",
      "blocks": ["<original source blocks>"]
    }
  ]
}

That distinguishes the native forward from a visible-link unfurl: the outer text contains only the optional comment, while the original author, blocks, and message actions live in Slack's server-generated share attachment.

Verification

  • make test
  • make build

Both pass with the repository's Go 1.25 Docker toolchain. The endpoint tests assert the form-encoded chat.shareMessage request, token form field, d cookie, source/destination parameters, optional rich-text comment blocks, Slack API error propagation, and OAuth rejection.

@upsetbit
upsetbit merged commit 4d8c225 into master Aug 31, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant