Repository navigation
Conversation
…view) There was no way to see which threads have unread replies: conversations_unreads covers channels/DMs only, and thread unread state lives in a separate per-thread cursor. Add a read-only tool backed by Slack's subscriptions.thread.getView — the endpoint behind the "Threads" view — which returns subscribed threads newest-activity-first with the user's unread replies per thread. - Edge call SubscriptionsThreadGetView (current_ts cursor, limit, optional server-side channel_id filter) with a typed response (threads with root_msg, latest_replies, unread_replies; counts; has_more; max_ts). - Tool conversations_threads: filter=unread|all (default unread), optional channel_id (#name/@name resolved), since (1d/7d/2w/1m, default 30d, or "all"), limit (default 20, max 200), include_replies (default 3, max 20) and cursor. Slack serves this view 10 threads per page regardless of limit and has no server-side unread filter, so the handler pages newest-first, stops at the time bound / end of view / a 50-page cap, and returns the continuation cursor in the last row so no scan is ever lossy. Output is CSV: Channel, ChannelID, ThreadTs, RootUser, RootTime, RootText, ReplyCount, UnreadReplies, LatestReplyTime, LastRead, Replies (unread replies if any, else latest, rendered "time author: text" via the same user/text pipeline as the other conversation tools), Cursor. - Registered only for browser session tokens (xoxc/xoxd), like the other edge-API tools; read-only annotation. - Tests: HTTP-level edge test with a fixture mirroring the live response; collectThreads paging/filter/bound/cursor/page-cap tests with a fake pager; parameter parsing and helper tests; ValidToolNames. Verified end-to-end through the MCP server over stdio against a live workspace: default call, cursor continuation (strictly older threads, no overlap), filter=all, channel filter, empty result, validation errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…th-safe Follow-ups from review of the new tool: - Drop the pointer to "conversations_mark with thread_ts" from the tool description and README: that parameter does not exist on master (it is proposed separately), and mcp-go ignores unknown arguments, so an LLM following the hint would have marked the whole channel read. - Compare Slack timestamps numerically (slackTsBefore) instead of as strings: a since window reaching before 2001 produced a 9-digit bound that stopped the scan immediately. - Fall back to the requested channel_id when root_msg.channel is absent; skip entries without any timestamp instead of letting them end the scan; emit no cursor when the limit is reached on the last thread of the last page. - Zero-match text now says why the scan stopped (time window, page cap, more available, end of view) and what was searched. - isSlackTimestamp requires exactly six fractional digits (Slack's format); filter declared with mcp.Enum; docs spell out d/w/m units and that filter/channel_id/since must be repeated when continuing with a cursor. - Row building and the zero-match message are pure functions with injected channel-name/render closures, and are unit-tested (CSV shape, unread-vs-latest replies, last N, ChannelID fallback, cursor placement, CSV round trip); collectThreads tests cover the numeric bound, the exhausted-view cursor and malformed entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
matt-temporal
added a commit
to matt-temporal/slack-mcp-server
that referenced
this pull request
Oct 5, 2026
|
Hey @panevesht, thanks so much for this! 🙌 Thread unreads were exactly the piece I was missing. In case anyone's worried about the red check: it's not this PR. The integration tests run against If you can't wait for the merge, I've put these two commits on top of current upstream in my fork and will keep it in sync: Just clone it and run |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New read-only tool
conversations_threads: lists the threads you are subscribed to — Slack's "Threads" view — newest activity first, with each thread's unread reply count and the unread (or latest) replies. By default it returns only threads with unread replies from the last 30 days.Until now there was no way to see which threads have unread replies:
conversations_unreadscovers channels/DMs only, and thread unread state lives in a separate per-thread read cursor. (Independent of #346, which adds per-thread marking; together they close the loop list → read → mark. The tool text deliberately does not reference that parameter so it is correct onmasteras-is.)Design
SubscriptionsThreadGetView→ Slack'ssubscriptions.thread.getView(the endpoint behind the Threads view; same undocumented browser-session family asclient.counts/saved.*), withcurrent_ts(cursor),limit, and the optional server-sidechannel_idfilter; typed response (threads[]withroot_msg,latest_replies,unread_replies;total_unread_replies,new_threads_count,has_more,max_ts).limitsays, and there is no server-side unread-only filter. The handler therefore pages newest-first and stops at the time bound (since), the end of the view, or a 50-page cap (500 threads), always returning the continuation cursor in the last row'sCursorcolumn — so no scan is lossy and the LLM can keep going.filter(unreaddefault |all, declared as an enum),channel_id(#name/@nameresolved like elsewhere),since(1d/7d/2w/1m— days/weeks/months, default30d, orall),limit(default 20, max 200),include_replies(default 3, max 20, 0 to omit),cursor(repeat the other filters when continuing). Slack timestamps are compared numerically; the cursor is validated as a Slack timestamp (six fractional digits). An empty result explains why the scan stopped (time window / page cap / end of view).Channel, ChannelID, ThreadTs, RootUser, RootTime, RootText, ReplyCount, UnreadReplies, LatestReplyTime, LastRead, Replies, Cursor. Root and replies go through the same user-resolution/text pipeline as the other conversation tools (convertMessagesFromHistory);Repliesholds the unread replies when there are any, otherwise the latest ones, astime author: textjoined by||.xoxc/xoxd), like the other edge-API tools;ReadOnlyHint. Uses the Tier-3 limiter between pages.docs/03tool lists on purpose to avoid conflicting with feat(saved): add saved_add and saved_delete tools #345, which already edits those lines; happy to add it there once one of the two lands.pkg/provider/edge/threads.goSubscriptionsThreadGetView+ response typespkg/provider/edge/threads_test.gopkg/provider/api.goSlackAPI+MCPSlackClientwrapperpkg/handler/threads.goThreadsHandler: params,collectThreads(paging / filter / bound / cursor / cap),buildThreadRows(pure, injected renderer),noThreadsMessagepkg/handler/threads_test.gopkg/server/server.go,pkg/server/server_test.goValidToolNamesREADME.mdToken compatibility
conversations_threadsxoxc/xoxdxoxpxoxbTesting
make test/go vetpass; unit tests as listed above.xoxc/xoxdon a live workspace: default call (5 unread threads in ~1 s, names/text/replies resolved), cursor continuation (strictly older threads, no overlap),filter=all since=1d include_replies=0, server-sidechannel_idfilter, a channel without threads (friendly "no matching threads"), and validation errors for badfilter/since/ unknown#channel.🤖 Generated with Claude Code