Mediates a Gadget's read-only access to a user's Slack workspace:
channels, direct messages, threads, members, and search. Runs as its own Cloudflare Worker and is
auto-discovered by the backend from its GATEKEEPER_SLACK binding.
This gatekeeper is read-only and never sends or modifies Slack data.
OAuth 2.0 using a user token (xoxp-…), requested via user_scope (not a bot token) so the
agent sees exactly what the connecting user can see — including private channels, DMs, and search.
Create a Slack app (https://api.slack.com/apps) and provide its client credentials to the worker
as CLIENT_ID / CLIENT_SECRET. For local dev, run-dev-server.js maps SLACK_CLIENT_ID /
SLACK_CLIENT_SECRET (e.g. from a root .dev.vars) into those vars.
App configuration:
- Redirect URL must match
<BASE_URL>/oauth, which in local dev defaults tohttp://localhost:8787/gatekeeper/slack/oauth. - Enable token rotation (OAuth & Permissions → Token Rotation). Tokens are then short-lived
(~12h) and refreshed via
oauth.v2.access?grant_type=refresh_token. Non-rotating tokens also work as a fallback (they're returned as-is). - Request the User Token Scopes the granted resources need (see below).
users:readis always requested for connected-account display and user-name resolution.
Access is granted at one of three granularities. Each grantable resource maps to a URL pattern that drives both consent (which OAuth scopes are requested) and routing:
| Granularity | URL pattern | Session type |
|---|---|---|
| Whole workspace | https://* (catch-all whole-instance) |
SlackWorkspaceSession |
| A conversation (channel, DM, or group DM) | https://app.slack.com/client/:teamId/:conversationId |
SlackConversation |
| A thread | https://*.slack.com/archives/:conversationId/:messageId |
SlackThread |
Workspace grants use the framework's account-wide https://* pattern; more-specific conversation
and thread URLs take precedence. Channels and DMs share one "Conversation" grant.
- Workspace:
team:read, conversation read scopes,search:read - Conversation: conversation read scopes,
search:read - Thread:
channels:history,groups:history,im:history,mpim:history - Always:
users:read
where the conversation read scopes are channels/groups/im/mpim :read + :history.
See src/types.d.ts for the full Session API (the agent-facing documentation). Highlights:
SlackWorkspaceSession:getInfo,listChannels,listDirectMessages,listUsers,getUser,getConversation,searchSlackConversation:getInfo,members,listMessages,getThread,search(conversation-scoped search is hard-restricted to the bound conversation regardless of query)SlackThread:getRoot,listReplies
List and search methods return paginated Cursor objects. Known mentions are rendered with readable
names.
pnpm --filter @gadgets/slack-gatekeeper build