-
Notifications
You must be signed in to change notification settings - Fork 176
feat: add conversations_unreads and conversations_mark tools #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: add conversations_unreads and conversations_mark tools #146
Conversation
…rieval - Uses ClientUserBoot to get all channels with LastRead/Latest in one API call - Filters channels where Latest > LastRead to find unreads - Prioritizes: DMs > group DMs > partner channels (ext-*) > internal - Only fetches message history for channels with actual unreads - Supports filtering by channel type and configurable limits Addresses issue korotovsky#114
- Switch from ClientUserBoot to ClientCounts API - ClientCounts returns HasUnreads boolean for all channels - Add ClientCounts to SlackAPI interface - Process Channels, MPIMs, and IMs separately
- Strip existing # prefix before adding to avoid ##name - Use stripped name for ext-/shared- prefix checks for partner type
- Add mentions_only parameter to conversations_unreads to filter channels to only those with @mentions (priority inbox) - Add conversations_mark tool to mark channels/DMs as read - Supports channel IDs, #channel names, and @username - If no timestamp provided, marks all messages as read
…nels - Add IsExtShared field to Channel struct in cache - Pass IsExtShared through mapChannel function - Use cached.IsExtShared to identify external/partner channels instead of checking for ext-/shared- name prefixes Note: Users may need to delete their channels cache file to repopulate with the new IsExtShared field.
korotovsky
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @saoudrizwan, thank you for the great contribution!
Since the new API method has been introduced and this MCP supports multiple slack token types, a question from my side: have you checked that ClientCounts also returns data if we provide xoxb or xoxp (as I assume your main setup is xoxc/xoxd pair since you are using undocumented APIs, this is is where this API works the best and has the most coverage)
| ch.logger.Debug("ConversationsUnreadsHandler called", zap.Any("params", request.Params)) | ||
|
|
||
| // Get optional parameters | ||
| includeMessages := request.GetBool("include_messages", true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we extract these parameters into struct + small func as other handlers do?
| } | ||
|
|
||
| // ConversationsMarkHandler marks a channel as read up to a specific timestamp | ||
| func (ch *ConversationsHandler) ConversationsMarkHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To ensure the safe rollout of new tools within existing agentic setups, I usually require any new tool that can add messages or perform write actions to be protected by a new environment variable. This safeguard is disabled by default and must be explicitly enabled by users who have updated Slack and intentionally want to use the tool. Otherwise, other users could be exposed to newly deployed tools with write access enabled by default, which I believe must be avoided for security reasons. Not sure if defining mcp.WithDestructiveHintAnnotation(false), is enough here, because we don't know how all (most) clients implement the MCP protocol.
| } | ||
|
|
||
| // categorizeChannel determines the type of channel for prioritization | ||
| func (ch *ConversationsHandler) categorizeChannel(id, name string, isIM, isMpIM, isPrivate, isExtShared bool) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be not used anywhere
| } | ||
|
|
||
| // getChannelDisplayName returns a human-readable channel name | ||
| func (ch *ConversationsHandler) getChannelDisplayName(id, name string, isIM, isMpIM bool, members []string, usersMap map[string]slack.User, channelsMaps *provider.ChannelsCache) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be not used anywhere
| continue | ||
| } | ||
|
|
||
| // Get channel info from cache to determine type and name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think would be better to reuse existing helper, it can handle more specific cases like groups and so on: https://github.com/korotovsky/slack-mcp-server/blob/master/pkg/handler/conversations.go#L711-L728
Adds two new tools for efficiently managing unread Slack messages 🚀
When you're in a lot of Slack channels (especially with external partner channels), keeping up with unreads becomes overwhelming. The existing
conversations_historytool requires you to know which channel to check. These new tools let you:New Tools
conversations_unreadsEfficiently retrieves unread messages across all channels using a single API call.
Parameters:
include_messagestruechannel_types"all""dm","group_dm","partner","internal", or"all"max_channels50max_messages_per_channel10mentions_onlyfalseOutput (CSV):
conversations_markMarks a channel or DM as read.
Parameters:
channel_id#channel-name, or@usernametsTechnical Implementation
Why
client.countsAPI?We initially tried using
ClientUserBootbut discovered it only returns channels in the user's sidebar, not all channels with unreads. Theclient.countsAPI (already implemented in the edge client) is what Slack's web client uses:HasUnreadsboolean andMentionCountfor ALL channels in one callChannels,MPIMs(group DMs),IMs(direct messages)Channel Categorization & Priority
Results are automatically sorted by priority:
dm) - Direct messages, highest prioritygroup_dm) - Multi-person direct messagespartner) - Externally shared channels (usesIsExtSharedmetadata)internal) - Everything elseCode Changes
pkg/provider/api.goIsExtSharedfield toChannelstructClientCountsmethod onSlackAPIinterfacemapChannelto pass throughIsExtSharedpkg/handler/conversations.goUnreadChannelstruct for CSV outputConversationsUnreadsHandlerwith priority sorting and filteringConversationsMarkHandlerwith channel name resolutionsortChannelsByPriorityhelperpkg/server/server.goExample Use Cases
1. Morning Inbox Review
2. Priority Inbox - Just @Mentions
3. Check Partner Channels Only
4. Quick Summary Without Messages
5. Mark Channel as Read
6. Triage Workflow
Breaking Changes
Users with existing channel caches will need to delete
~/Library/Caches/slack-mcp-server/channels_cache_v2.json(or equivalent) to repopulate with the newIsExtSharedfield. Otherwise, partner channel detection won't work until the cache is refreshed.Test Plan
conversations_unreadsreturns prioritized list of unread channelschannel_typesfilter correctly filters by dm/group_dm/partner/internalmentions_only: truefilters to only channels with @mentionsinclude_messages: falsereturns summary without fetching message contentIsExtSharedmetadata (not name prefix)conversations_markmarks channels as read using channel IDconversations_markresolves#channel-nameto channel IDconversations_markresolves@usernameto DM channel IDconversations_markwithout timestamp marks all messages as read