Esta página se muestra en inglés. Una traducción al español está en curso.
SkillsMay 8, 2026·4 min de lectura

Slack Block Kit Builder — Helper for AI-Generated Messages

Python helper that converts LLM markdown to valid Slack Block Kit JSON. Headers, sections, dividers, buttons. Auto-splits past the 3001-char limit.

Listo para agents

Staging seguro para este activo

Este activo primero queda en staging. El prompt copiado pide inspeccionar los archivos staged antes de activar scripts, config MCP o config global.

Stage only · 29/100Política: staging
Superficie agent
Cualquier agent MCP/CLI
Tipo
Skill
Instalación
Stage only
Confianza
Confianza: Community
Entrada
Asset
Comando de staging seguro
npx -y tokrepo@latest install e50d6b32-9054-4bb8-a230-02546da7f852 --target codex

Primero deja archivos en staging; la activación requiere revisar el README y el plan staged.

Introducción

This is a small Python helper that takes plain markdown from an LLM and produces valid Slack Block Kit JSON — header, section, divider, fields, buttons, image — automatically chunking text past Slack's 3001-char per-block limit. Slack messages without Block Kit look terrible; messages with Block Kit require fragile hand-built JSON. This bridges them. Best for: AI bots posting summaries, alerts, daily digests, and reports to Slack channels. Works with: Slack Bolt SDK (Python/JS), Slack chat.postMessage REST. Setup time: 5 minutes.


Helper code

from typing import Iterable

def md_to_blocks(text: str) -> list[dict]:
    blocks = []
    for raw in text.split("\n\n"):
        chunk = raw.strip()
        if not chunk:
            continue
        if chunk.startswith("# "):
            blocks.append({"type": "header", "text": {"type": "plain_text", "text": chunk[2:].strip()[:150]}})
        elif chunk == "---":
            blocks.append({"type": "divider"})
        else:
            for piece in _split_3000(chunk):
                blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": piece}})
    return blocks

def _split_3000(s: str, limit: int = 2900) -> Iterable[str]:
    while len(s) > limit:
        cut = s.rfind("\n", 0, limit)
        if cut == -1:
            cut = s.rfind(" ", 0, limit) or limit
        yield s[:cut]
        s = s[cut:].lstrip()
    if s:
        yield s

def add_actions(blocks: list[dict], buttons: list[tuple[str, str, str]]) -> list[dict]:
    if not buttons:
        return blocks
    blocks.append({
        "type": "actions",
        "elements": [
            {"type": "button", "text": {"type": "plain_text", "text": label},
             "action_id": action_id, "url": url}
            for label, action_id, url in buttons
        ],
    })
    return blocks

Usage with Bolt

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import os

app = App(token=os.environ["SLACK_BOT_TOKEN"])
llm_text = '''# Daily AI Digest

*5 new MCP servers* shipped on TokRepo today.

- Stripe MCP — payments
- Datadog MCP — observability
- Linear MCP — issue tracker

---

Top reaction: stripe-mcp got 47 stars in 6 hours.'''

blocks = md_to_blocks(llm_text)
blocks = add_actions(blocks, [("Open TokRepo", "open_tokrepo", "https://tokrepo.com/explore")])

app.client.chat_postMessage(channel="#ai-digest", blocks=blocks, text="Daily AI Digest")

Slack Block Kit limits (cheat sheet)

Item Limit
Blocks per message 50
Section text 3,000 chars
Header text 150 chars
Action elements per actions block 25
Total message size 40 KB

FAQ

Q: Why mrkdwn instead of markdown? A: Slack's mrkdwn is a subset — *bold* (one asterisk, not two), _italic_, <url|label> for links, no headings, no tables. The helper outputs mrkdwn-flavored text. Hand-converting LLM markdown is the part this saves.

Q: Does the LLM need to know about Block Kit? A: No — that's the point. Prompt it for normal markdown. The helper handles the conversion. Hand the LLM Block Kit JSON in the prompt and outputs become brittle and verbose.

Q: What if the message is over 50 blocks? A: Slack rejects with invalid_blocks error. Send as a thread — first message gets the first 50 blocks, then chat.postMessage with thread_ts for follow-ups. Or split into multiple top-level messages with day/section headers.


Quick Use

  1. pip install slack-bolt
  2. Drop md_to_blocks() into your bot module
  3. Pipe LLM markdown straight into chat.postMessage(blocks=md_to_blocks(text))

Intro

This is a small Python helper that takes plain markdown from an LLM and produces valid Slack Block Kit JSON — header, section, divider, fields, buttons, image — automatically chunking text past Slack's 3001-char per-block limit. Slack messages without Block Kit look terrible; messages with Block Kit require fragile hand-built JSON. This bridges them. Best for: AI bots posting summaries, alerts, daily digests, and reports to Slack channels. Works with: Slack Bolt SDK (Python/JS), Slack chat.postMessage REST. Setup time: 5 minutes.


Helper code

from typing import Iterable

def md_to_blocks(text: str) -> list[dict]:
    blocks = []
    for raw in text.split("\n\n"):
        chunk = raw.strip()
        if not chunk:
            continue
        if chunk.startswith("# "):
            blocks.append({"type": "header", "text": {"type": "plain_text", "text": chunk[2:].strip()[:150]}})
        elif chunk == "---":
            blocks.append({"type": "divider"})
        else:
            for piece in _split_3000(chunk):
                blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": piece}})
    return blocks

def _split_3000(s: str, limit: int = 2900) -> Iterable[str]:
    while len(s) > limit:
        cut = s.rfind("\n", 0, limit)
        if cut == -1:
            cut = s.rfind(" ", 0, limit) or limit
        yield s[:cut]
        s = s[cut:].lstrip()
    if s:
        yield s

def add_actions(blocks: list[dict], buttons: list[tuple[str, str, str]]) -> list[dict]:
    if not buttons:
        return blocks
    blocks.append({
        "type": "actions",
        "elements": [
            {"type": "button", "text": {"type": "plain_text", "text": label},
             "action_id": action_id, "url": url}
            for label, action_id, url in buttons
        ],
    })
    return blocks

Usage with Bolt

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import os

app = App(token=os.environ["SLACK_BOT_TOKEN"])
llm_text = '''# Daily AI Digest

*5 new MCP servers* shipped on TokRepo today.

- Stripe MCP — payments
- Datadog MCP — observability
- Linear MCP — issue tracker

---

Top reaction: stripe-mcp got 47 stars in 6 hours.'''

blocks = md_to_blocks(llm_text)
blocks = add_actions(blocks, [("Open TokRepo", "open_tokrepo", "https://tokrepo.com/explore")])

app.client.chat_postMessage(channel="#ai-digest", blocks=blocks, text="Daily AI Digest")

Slack Block Kit limits (cheat sheet)

Item Limit
Blocks per message 50
Section text 3,000 chars
Header text 150 chars
Action elements per actions block 25
Total message size 40 KB

FAQ

Q: Why mrkdwn instead of markdown? A: Slack's mrkdwn is a subset — *bold* (one asterisk, not two), _italic_, <url|label> for links, no headings, no tables. The helper outputs mrkdwn-flavored text. Hand-converting LLM markdown is the part this saves.

Q: Does the LLM need to know about Block Kit? A: No — that's the point. Prompt it for normal markdown. The helper handles the conversion. Hand the LLM Block Kit JSON in the prompt and outputs become brittle and verbose.

Q: What if the message is over 50 blocks? A: Slack rejects with invalid_blocks error. Send as a thread — first message gets the first 50 blocks, then chat.postMessage with thread_ts for follow-ups. Or split into multiple top-level messages with day/section headers.


Source & Thanks

Pattern compiled from Slack Block Kit + Bolt SDK.

Bolt MIT-licensed, helper Apache-2.0.

🙏

Fuente y agradecimientos

Pattern compiled from Slack Block Kit + Bolt SDK.

Bolt MIT-licensed, helper Apache-2.0.

Discusión

Inicia sesión para unirte a la discusión.
Aún no hay comentarios. Sé el primero en compartir tus ideas.

Activos relacionados