Add bot token (xoxb) support

- Add support for bot tokens as an alternative authentication method
- Conditionally register search tool only for non-bot tokens (bots cannot use search.messages API)
- Update isOAuth definition to include both xoxp and xoxb tokens for cleaner implementation
- Add bot token configuration to manifest-dxt.json for DXT extension
- Document bot token limitations in README and authentication setup guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sanghoon Hong
2025-10-18 22:09:39 +09:00
parent 7920208b78
commit bb9469d3e6
7 changed files with 131 additions and 19 deletions
+10 -1
View File
@@ -1,3 +1,12 @@
# User OAuth Token (starts with xoxp-)
SLACK_MCP_XOXP_TOKEN="" # Replace with your user OAuth token
# Bot OAuth Token (starts with xoxb-)
SLACK_MCP_XOXB_TOKEN="" # Replace with your bot token
# Session-based tokens (browser tokens)
SLACK_MCP_XOXC_TOKEN="" # Replace with your actual token
SLACK_MCP_XOXD_TOKEN="" # Replace with your actual token
SLACK_MCP_SSE_API_KEY="" # Replace with your actual API key
# SSE API Key for authentication
SLACK_MCP_SSE_API_KEY="" # Replace with your actual API key
+4 -1
View File
@@ -60,6 +60,8 @@ Add a message to a public channel, private channel, or direct message (DM, or IM
### 4. conversations_search_messages
Search messages in a public channel, private channel, or direct message (DM, or IM) conversation using filters. All filters are optional, if not provided then search_query is required.
> **Note**: This tool is not available when using bot tokens (`xoxb-*`). Bot tokens cannot use the `search.messages` API.
- **Parameters:**
- `search_query` (string, optional): Search query to filter messages. Example: 'marketing report' or full URL of Slack message e.g. 'https://slack.com/archives/C1234567890/p1234567890123456', then the tool will return a single message matching given URL, herewith all other parameters will be ignored.
- `filter_in_channel` (string, optional): Filter messages in a specific channel by its ID or name. Example: `C1234567890` or `#general`. If not provided, all channels will be searched.
@@ -123,6 +125,7 @@ Fetches a CSV directory of all users in the workspace.
| `SLACK_MCP_XOXC_TOKEN` | Yes* | `nil` | Slack browser token (`xoxc-...`) |
| `SLACK_MCP_XOXD_TOKEN` | Yes* | `nil` | Slack browser cookie `d` (`xoxd-...`) |
| `SLACK_MCP_XOXP_TOKEN` | Yes* | `nil` | User OAuth token (`xoxp-...`) — alternative to xoxc/xoxd |
| `SLACK_MCP_XOXB_TOKEN` | Yes* | `nil` | Bot token (`xoxb-...`) — alternative to xoxp/xoxc/xoxd. Bot has limited access (invited channels only, no search) |
| `SLACK_MCP_PORT` | No | `13080` | Port for the MCP server to listen on |
| `SLACK_MCP_HOST` | No | `127.0.0.1` | Host for the MCP server to listen on |
| `SLACK_MCP_API_KEY` | No | `nil` | Bearer token for SSE and HTTP transports |
@@ -139,7 +142,7 @@ Fetches a CSV directory of all users in the workspace.
| `SLACK_MCP_CHANNELS_CACHE` | No | `.channels_cache_v2.json` | Path to the channels cache file. Used to cache Slack channel information to avoid repeated API calls on startup. |
| `SLACK_MCP_LOG_LEVEL` | No | `info` | Log-level for stdout or stderr. Valid values are: `debug`, `info`, `warn`, `error`, `panic` and `fatal` |
*You need either `xoxp` **or** both `xoxc`/`xoxd` tokens for authentication.
*You need one of: `xoxp` (user), `xoxb` (bot), or both `xoxc`/`xoxd` tokens for authentication.
### Limitations matrix & Cache
+20 -4
View File
@@ -2,7 +2,11 @@
Open up your Slack in your browser and login.
#### Lookup `SLACK_MCP_XOXC_TOKEN`
> **Note**: You only need one of the following: an `xoxp-*` User OAuth token, an `xoxb-*` Bot token, or both `xoxc-*` and `xoxd-*` session tokens. User/Bot tokens are more secure and do not require a browser session. If multiple are provided, priority is `xoxp` > `xoxb` > `xoxc/xoxd`.
#### Option 1: Using `SLACK_MCP_XOXC_TOKEN`/`SLACK_MCP_XOXD_TOKEN` (Browser session)
##### Lookup `SLACK_MCP_XOXC_TOKEN`
- Open your browser's Developer Console.
- In Firefox, under `Tools -> Browser Tools -> Web Developer tools` in the menu bar
@@ -16,7 +20,7 @@ Open up your Slack in your browser and login.
Token value is printed right after the executed command (it starts with
`xoxc-`), save it somewhere for now.
#### Lookup `SLACK_MCP_XOXD_TOKEN`
##### Lookup `SLACK_MCP_XOXD_TOKEN`
- Switch to "Application" tab and select "Cookies" in the left navigation pane.
- Find the cookie with the name `d`. That's right, just the letter `d`.
@@ -24,7 +28,7 @@ Token value is printed right after the executed command (it starts with
- Press Ctrl+C or Cmd+C to copy it's value to clipboard.
- Save it for later.
#### Alternative: Using `SLACK_MCP_XOXP_TOKEN` (User OAuth)
#### Option 2: Using `SLACK_MCP_XOXP_TOKEN` (User OAuth)
Instead of using browser-based tokens (`xoxc`/`xoxd`), you can use a User OAuth token:
@@ -47,6 +51,7 @@ Instead of using browser-based tokens (`xoxc`/`xoxd`), you can use a User OAuth
3. Install the app to your workspace
4. Copy the "User OAuth Token" (starts with `xoxp-`)
##### App manifest (preconfigured scopes)
To create the app from a manifest with permissions preconfigured, use the following code snippet:
```json
@@ -81,6 +86,17 @@ To create the app from a manifest with permissions preconfigured, use the follow
}
```
> **Note**: You only need **either** XOXP token **or** both XOXC/XOXD tokens. XOXP user tokens are more secure and don't require browser session extraction.
#### Option 3: Using `SLACK_MCP_XOXB_TOKEN` (Bot Token)
You can also use a Bot token instead of a User token:
1. Go to [api.slack.com/apps](https://api.slack.com/apps) and create a new app
2. Under "OAuth & Permissions", add Bot Token Scopes (same as User scopes above, except `search:read`)
3. Install the app to your workspace
4. Copy the "Bot User OAuth Token" (starts with `xoxb-`)
5. **Important**: Bot must be invited to channels for access
> **Note**: Bot tokens cannot use `search.messages` API, so `conversations_search_messages` tool will not be available.
See next: [Installation](02-installation.md)
+24 -3
View File
@@ -10,8 +10,8 @@ For [Claude Desktop](https://claude.ai/download) users, you can use the DXT exte
2. Click on the `Extensions` tab.
3. Drag and drop the downloaded .dxt file to install it and click "Install".
5. Fill all required configuration fields
- Authentication method: `xoxc/xoxd` or `xoxp`.
- Value for `SLACK_MCP_XOXC_TOKEN` and `SLACK_MCP_XOXD_TOKEN` in case of `xoxc/xoxd` method, or `SLACK_MCP_XOXP_TOKEN` in case of `xoxp`.
- Authentication method: `xoxc/xoxd`, `xoxp`, or `xoxb`.
- Value for `SLACK_MCP_XOXC_TOKEN` and `SLACK_MCP_XOXD_TOKEN` in case of `xoxc/xoxd` method, `SLACK_MCP_XOXP_TOKEN` in case of `xoxp`, or `SLACK_MCP_XOXB_TOKEN` in case of `xoxb`.
- You may also enable `Add Message Tool` to allow posting messages to channels.
- You may also change User-Agent if needed if you have Enterprise Slack.
6. Enable MCP Server.
@@ -27,6 +27,7 @@ Below are prepared configurations:
- `npx` and `xoxc/xoxd` method: [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=slack-mcp-server&config=eyJjb21tYW5kIjogIm5weCAteSBzbGFjay1tY3Atc2VydmVyQGxhdGVzdCAtLXRyYW5zcG9ydCBzdGRpbyIsImVudiI6IHsiU0xBQ0tfTUNQX1hPWENfVE9LRU4iOiAieG94Yy0uLi4iLCAiU0xBQ0tfTUNQX1hPWERfVE9LRU4iOiAieG94ZC0uLi4ifSwiZGlzYWJsZWQiOiBmYWxzZSwiYXV0b0FwcHJvdmUiOiBbXX0%3D)
- `npx` and `xoxp` method: [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=slack-mcp-server&config=eyJjb21tYW5kIjogIm5weCAteSBzbGFjay1tY3Atc2VydmVyQGxhdGVzdCAtLXRyYW5zcG9ydCBzdGRpbyIsImVudiI6IHsiU0xBQ0tfTUNQX1hPWFBfVE9LRU4iOiAieG94cC0uLi4ifSwiZGlzYWJsZWQiOiBmYWxzZSwiYXV0b0FwcHJvdmUiOiBbXX0%3D)
- `npx` and `xoxb` method: [![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=slack-mcp-server&config=eyJjb21tYW5kIjogIm5weCAteSBzbGFjay1tY3Atc2VydmVyQGxhdGVzdCAtLXRyYW5zcG9ydCBzdGRpbyIsImVudiI6IHsiU0xBQ0tfTUNQX1hPWEJfVE9LRU4iOiAieG94Yi0uLi4ifSwiZGlzYWJsZWQiOiBmYWxzZSwiYXV0b0FwcHJvdmUiOiBbXX0%3D)
> [!IMPORTANT]
> Remember to replace tokens in the configuration with your own tokens, as they are just examples.
@@ -60,7 +61,27 @@ Open your `claude_desktop_config.json` and add the mcp server to the list of `mc
}
```
**Option 2: Using XOXC/XOXD Tokens**
**Option 2: Using XOXB Token (Bot)**
``` json
{
"mcpServers": {
"slack": {
"command": "npx",
"args": [
"-y",
"slack-mcp-server@latest",
"--transport",
"stdio"
],
"env": {
"SLACK_MCP_XOXB_TOKEN": "xoxb-..."
}
}
}
}
```
**Option 3: Using XOXC/XOXD Tokens**
``` json
{
"mcpServers": {
+10 -2
View File
@@ -31,6 +31,7 @@
"SLACK_MCP_XOXC_TOKEN": "${user_config.xoxc_token}",
"SLACK_MCP_XOXD_TOKEN": "${user_config.xoxd_token}",
"SLACK_MCP_XOXP_TOKEN": "${user_config.xoxp_token}",
"SLACK_MCP_XOXB_TOKEN": "${user_config.xoxb_token}",
"SLACK_MCP_USER_AGENT": "${user_config.user_agent}",
"SLACK_MCP_ADD_MESSAGE_TOOL": "${user_config.add_message_tool}",
"SLACK_MCP_USERS_CACHE": "${HOME}/.users_cache.json",
@@ -51,8 +52,8 @@
"user_config": {
"auth_method": {
"type": "string",
"title": "Authentication Method: xoxc/xoxd or xoxp",
"description": "Select the authentication method for the Slack MCP Server. You can choose between 'xoxc/xoxd' (browser based) or 'xoxp' (OAuth2).",
"title": "Authentication Method: xoxc/xoxd, xoxp, or xoxb",
"description": "Select the authentication method for the Slack MCP Server. You can choose between 'xoxc/xoxd' (browser based), 'xoxp' (User OAuth), or 'xoxb' (Bot OAuth).",
"default": "",
"required": true
},
@@ -77,6 +78,13 @@
"sensitive": true,
"required": false
},
"xoxb_token": {
"type": "string",
"title": "Value for xoxb token (Bot)",
"description": "Bot OAuth token for channel-specific automation. Bot must be invited to channels for access. Note: Search functionality is not available with bot tokens.",
"sensitive": true,
"required": false
},
"user_agent": {
"type": "string",
"title": "Value for user agent",
+57 -6
View File
@@ -79,6 +79,7 @@ type MCPSlackClient struct {
isEnterprise bool
isOAuth bool
isBotToken bool
teamEndpoint string
}
@@ -135,6 +136,13 @@ func NewMCPSlackClient(authProvider auth.Provider, logger *zap.Logger) (*MCPSlac
}
isEnterprise := authResp.EnterpriseID != ""
token := authProvider.SlackToken()
// Token type detection
// isOAuth: Official OAuth tokens (xoxp or xoxb) - uses Standard API
// isBotToken: Bot token - determines feature availability (e.g., search)
isOAuth := strings.HasPrefix(token, "xoxp-") || strings.HasPrefix(token, "xoxb-")
isBotToken := strings.HasPrefix(token, "xoxb-")
return &MCPSlackClient{
slackClient: slackClient,
@@ -142,7 +150,8 @@ func NewMCPSlackClient(authProvider auth.Provider, logger *zap.Logger) (*MCPSlac
authResponse: authResponse,
authProvider: authProvider,
isEnterprise: isEnterprise,
isOAuth: strings.HasPrefix(authProvider.SlackToken(), "xoxp-"),
isOAuth: isOAuth,
isBotToken: isBotToken,
teamEndpoint: authResp.URL,
}, nil
}
@@ -268,6 +277,10 @@ func (c *MCPSlackClient) AuthResponse() *slack.AuthTestResponse {
return c.authResponse
}
func (c *MCPSlackClient) IsBotToken() bool {
return c.isBotToken
}
func (c *MCPSlackClient) Raw() struct {
Slack *slack.Client
Edge *edge.Client
@@ -287,8 +300,23 @@ func New(transport string, logger *zap.Logger) *ApiProvider {
err error
)
// Check for XOXP token first (User OAuth)
// Read all environment variables
xoxpToken := os.Getenv("SLACK_MCP_XOXP_TOKEN")
xoxbToken := os.Getenv("SLACK_MCP_XOXB_TOKEN")
xoxcToken := os.Getenv("SLACK_MCP_XOXC_TOKEN")
xoxdToken := os.Getenv("SLACK_MCP_XOXD_TOKEN")
// Warn if both user and bot tokens are set
if xoxpToken != "" && xoxbToken != "" {
logger.Warn(
"Both SLACK_MCP_XOXP_TOKEN and SLACK_MCP_XOXB_TOKEN are set. "+
"Using User token (xoxp) for full features. "+
"Bot token will be ignored.",
zap.String("context", "console"),
)
}
// Priority 1: XOXP token (User OAuth)
if xoxpToken != "" {
authProvider, err = auth.NewValueAuth(xoxpToken, "")
if err != nil {
@@ -298,12 +326,24 @@ func New(transport string, logger *zap.Logger) *ApiProvider {
return newWithXOXP(transport, authProvider, logger)
}
// Fall back to XOXC/XOXD tokens (session-based)
xoxcToken := os.Getenv("SLACK_MCP_XOXC_TOKEN")
xoxdToken := os.Getenv("SLACK_MCP_XOXD_TOKEN")
// Priority 2: XOXB token (Bot)
if xoxbToken != "" {
authProvider, err = auth.NewValueAuth(xoxbToken, "")
if err != nil {
logger.Fatal("Failed to create auth provider with XOXB token", zap.Error(err))
}
logger.Info("Using Bot token authentication",
zap.String("context", "console"),
zap.String("token_type", "xoxb"),
)
return newWithXOXB(transport, authProvider, logger)
}
// Priority 3: XOXC/XOXD tokens (session-based)
if xoxcToken == "" || xoxdToken == "" {
logger.Fatal("Authentication required: Either SLACK_MCP_XOXP_TOKEN (User OAuth) or both SLACK_MCP_XOXC_TOKEN and SLACK_MCP_XOXD_TOKEN (session-based) environment variables must be provided")
logger.Fatal("Authentication required: Either SLACK_MCP_XOXP_TOKEN, SLACK_MCP_XOXB_TOKEN, or both SLACK_MCP_XOXC_TOKEN and SLACK_MCP_XOXD_TOKEN must be provided")
}
authProvider, err = auth.NewValueAuth(xoxcToken, xoxdToken)
@@ -356,6 +396,12 @@ func newWithXOXP(transport string, authProvider auth.ValueAuth, logger *zap.Logg
}
}
func newWithXOXB(transport string, authProvider auth.ValueAuth, logger *zap.Logger) *ApiProvider {
// Bot tokens do not support demo mode, but otherwise share the same
// initialization logic as user OAuth tokens.
return newWithXOXP(transport, authProvider, logger)
}
func newWithXOXC(transport string, authProvider auth.ValueAuth, logger *zap.Logger) *ApiProvider {
var (
client *MCPSlackClient
@@ -676,6 +722,11 @@ func (ap *ApiProvider) Slack() SlackAPI {
return ap.client
}
func (ap *ApiProvider) IsBotToken() bool {
client, ok := ap.client.(*MCPSlackClient)
return ok && client != nil && client.IsBotToken()
}
func mapChannel(
id, name, nameNormalized, topic, purpose, user string,
members []string,
+6 -2
View File
@@ -93,7 +93,7 @@ func NewMCPServer(provider *provider.ApiProvider, logger *zap.Logger) *MCPServer
),
), conversationsHandler.ConversationsAddMessageHandler)
s.AddTool(mcp.NewTool("conversations_search_messages",
conversationsSearchTool := mcp.NewTool("conversations_search_messages",
mcp.WithDescription("Search messages in a public channel, private channel, or direct message (DM, or IM) conversation using filters. All filters are optional, if not provided then search_query is required."),
mcp.WithString("search_query",
mcp.Description("Search query to filter messages. Example: 'marketing report' or full URL of Slack message e.g. 'https://slack.com/archives/C1234567890/p1234567890123456', then the tool will return a single message matching given URL, herewith all other parameters will be ignored."),
@@ -133,7 +133,11 @@ func NewMCPServer(provider *provider.ApiProvider, logger *zap.Logger) *MCPServer
mcp.DefaultNumber(20),
mcp.Description("The maximum number of items to return. Must be an integer between 1 and 100."),
),
), conversationsHandler.ConversationsSearchHandler)
)
// Only register search tool for non-bot tokens (bot tokens cannot use search.messages API)
if !provider.IsBotToken() {
s.AddTool(conversationsSearchTool, conversationsHandler.ConversationsSearchHandler)
}
channelsHandler := handler.NewChannelsHandler(provider, logger)