Skip to main content

Slack

List channels, read history, post a message, reply in a thread. Both writes stop and ask a human.

npm i @forge/tools-slack

Tools

ToolEffectApprovalNotes
slack_list_channelsreadneverCursor-paginated; reports truncated
slack_read_historyreadneverOne channel, newest first
slack_post_messageexternal-writealwaysIdempotency key required
slack_reply_in_threadexternal-writealwaysNeeds the parent message's thread_ts

Wire it up

import { createAgent } from "@forge/agentkit/providers";
import { createStaticCredentialResolver } from "@forge/agentkit/tools";
import { createSlackToolkit } from "@forge/tools-slack";

const agent = createAgent({
manifest: {
id: "helper",
name: "Helper",
instructions: "Answer in the thread you were asked in. Be brief.",
modelPolicy: { role: "smart" },
},
tools: [
createSlackToolkit({
credentialRef: "slack-bot",
resolver: createStaticCredentialResolver({ "slack-bot": process.env.SLACK_BOT_TOKEN ?? "" }),
}),
],
});

Credentials and scopes

A bot token (xoxb-…) from your app's OAuth & Permissions page. The bot must be invited to a channel before it can read or post there — a missing invitation is not_in_channel, not a permissions error.

What the agent should doScope
List channelschannels:read
Read historychannels:history
Post and replychat:write

Behaviour worth knowing

Slack answers 200 with ok: false. This is the mistake this package exists to not make. An integration that checks the HTTP status alone treats channel_not_found, not_in_channel and invalid_auth as successes — and the model then reports that it posted a message which never arrived. The envelope is read, not the status.

ratelimited is separated from the rest, and is retryable, so the model waits rather than retrying with different arguments. invalid_auth is unauthorized and not retryable: trying again cannot fix a token.

Message text is untrusted. It arrives fenced. Slack is the most likely place for a prompt-injection attempt to reach an agent, because anyone in the channel can type one.

Limits

No file upload (multipart to a second host), no reactions, no user lookup, no channel creation, no direct messages. Reading and answering in a channel is the useful core; the rest is additive.

An agent that only answers when asked needs a trigger — an event subscription that starts a run. That is not part of this package: it is a deployment's own HTTP endpoint, and it needs Slack's request signature verified before it starts anything.