213 Commits

Author SHA1 Message Date
George Bashi 3b846b9607 fix: merge edge and standard API results for Enterprise Grid channels
On Enterprise Grid with restricted teams, the edge API's SearchChannels
and ClientUserBoot goroutines may fail while IMList succeeds. Since
goroutine errors are non-fatal (partial results kept), the edge API
returns only DMs with err=nil — so the standard API fallback never
triggers, and public/private channels are missing from the cache.

Fix: always supplement edge API results with full pagination of the
standard conversations.list API, deduplicating by channel ID. This
ensures we get DMs/MPIMs from the edge API AND public/private channels
from the standard API regardless of which edge goroutines fail.
2026-03-03 12:36:59 -08:00
George Bashi ba07a6fc37 refactor: fetch all channel types in a single API call
GetChannels previously called GetChannelsType in a loop, once per
channel type (mpim, im, public_channel, private_channel). Both the
standard conversations.list API and the edge API support fetching
all types in a single request, so this loop was making 4 redundant
round-trips.

Extract getChannelsMultiType to accept a slice of channel types,
and call it once with all types from GetChannels. GetChannelsType
is preserved as a thin wrapper for callers that need single-type
fetches.
2026-03-03 11:31:39 -08:00
George Bashi 9c93f5d4cb fix: prevent null cache poisoning from permanently hiding channels
When the Slack API returned no channels (due to upstream failures),
json.MarshalIndent(nil) produced "null" which was written to the
cache file. On subsequent startups, json.Unmarshal("null") into a
slice succeeds with no error, producing an empty slice that was
treated as valid cached data — permanently serving empty results
until the cache TTL expired.

Three defense-in-depth guards:
- Don't write cache file when channel list is empty
- Treat empty/null unmarshaled channels cache as invalid (refetch)
- Same empty-cache guard for users cache (consistency)
2026-03-03 11:31:39 -08:00
George Bashi 2caae3e0ef fix: fall back to standard API when edge API fails on Enterprise Grid
For Enterprise Grid workspaces with xoxc/xoxd session tokens, the
server exclusively used the edge API for channel listing. If the
edge API returned an error, the server propagated it with no
channels.

Add a fallback to the standard conversations.list API, which also
works with xoxc tokens in many Enterprise Grid setups. Cache the
edge failure in an edgeFailed flag so that subsequent pagination
calls go straight to the standard API instead of retrying and
failing on every page.
2026-03-03 11:31:39 -08:00
George Bashi cdcd9fe4d9 fix(edge): collect partial results instead of discarding on errors
The edge API's GetConversationsContext runs 3 concurrent goroutines
(ClientUserBoot, IMList, SearchChannels), then calls ClientCounts
and ConversationsGenericInfo for MPIM discovery.

Previously, ANY failure in the pipeline discarded ALL successfully
fetched channels:
- A goroutine error discarded results from other goroutines
- ClientCounts failing (e.g. team_is_restricted) discarded everything
- ConversationsGenericInfo failing discarded everything

Now errors are non-fatal: channels from successful sources are kept.
Only if all primary sources fail (0 channels collected) is the error
propagated. MPIM discovery failures are always non-fatal since MPIMs
are supplementary to the main channel/IM/DM data.
2026-03-03 11:31:39 -08:00
Erik Jankovič 6f7acbdc3e chore: allow to filter out users_search tool
Signed-off-by: Erik Jankovič <erik.jankovic@gmail.com>
2026-02-27 01:32:36 +01:00
Manuel Kreutz f50473c33c perf: use users.conversations instead of conversations.list for xoxp scan (#7)
Switch the xoxp unread scan from conversations.list to users.conversations.
users.conversations returns only channels the calling user is a member of,
eliminating non-member public channels and closed DMs that cannot have unreads.

Empirically tested on a 2700-channel workspace:
- 37% fewer channels to scan (1724 vs 2737)
- 85% reduction for public channels (156 vs 1046)
- Zero real unreads missed (verified against client.counts ground truth)
- Both methods return identical channel sets for member channels
- The 10 'missed' channels are all archived with never-visited markers
2026-02-23 09:40:37 -06:00
Manuel Kreutz b540e8ee16 fix: add rate limiting and retry to includeMessages history calls (#5)
The includeMessages loop in getUnreadsViaConversationsInfo calls
GetConversationHistoryContext with no rate limiter and no retry — the
only conversations.history call in the xoxp path not wrapped in
CallWithRetry.

When include_messages=true and multiple unread channels are found,
these calls fire unthrottled, risking 429s that silently skip channels
and lose messages.

Fix: add a local Tier 3 rate limiter and wrap in CallWithRetry,
matching the pattern used everywhere else in the xoxp scan path.
2026-02-19 16:25:43 -06:00
Manuel Kreutz 7224ff65ca fix: add proactive rate limiting and retry for xoxp conversations.info/history calls (#4)
The xoxp fallback in conversations_unreads calls GetConversationInfoContext
per channel to check for unreads. slack-go's standard Client does NOT
auto-retry on HTTP 429 — it returns *slack.RateLimitedError to the caller.

The scan loop handled ALL errors with continue at Debug level, so rate
limit errors were silently swallowed, causing non-deterministic results
(niebloomj confirmed 279/400 channels hit rate limits on their workspace).

This commit adds:
- limiter.CallWithRetry[T] generic helper in pkg/limiter: proactive
  rate.Limiter.Wait + retry via caller-provided retryAfter callback
  (no slack-go dependency in the limiter package)
- slackRetryAfter helper in the handler to extract RetryAfter from
  *slack.RateLimitedError
- Applied to both GetConversationInfoContext and
  GetConversationHistoryContext in scanTypeGroupForUnreads
- rateLimited counter surfaced in scan metadata so the LLM response
  includes a WARNING when channels are skipped
- Rate limit errors promoted from Debug to Warn level
- 8 unit tests in pkg/limiter covering retry logic, exhaustion,
  context cancellation, and typed return values
2026-02-16 15:55:50 -06:00
Manuel Kreutz 43a2ec1145 fix: token-type routing and validation for conversations_unreads (#3)
* fix: pre-check token type to avoid faulty ClientCounts call

client.counts only works with browser session tokens (xoxc/xoxd). OAuth
tokens (xoxp) get 'not_allowed_token_type' and bot tokens (xoxb) have no
concept of user-level unreads.

Instead of calling ClientCounts and catching the error after the fact,
pre-check the token type using the existing IsOAuth()/IsBotToken()
infrastructure and route directly to the appropriate path:
- xoxc/xoxd: fast path via client.counts (unchanged)
- xoxp: conversations.info fallback (no wasted API call)
- xoxb: clear error message

Follows the same pattern used by SearchUsers and GetConversationsContext
which already branch on token type.

Addresses review comments on korotovsky/slack-mcp-server#171.

* fix: exclude conversations_unreads tool for bot tokens

Bot tokens (xoxb) don't support unread tracking — it's a user-level
concept. Don't register the tool at all for bot token users, matching
the same pattern used for conversations_search_messages.

This gives a clean UX: bot users simply don't see the tool, rather
than getting a runtime error.

* fix: cap xoxp fallback scan depth and document limitations

The xoxp path previously scanned ALL channels (~2000+ API calls on large
workspaces). Slack's API has no bulk unread endpoint for xoxp tokens —
every other open-source implementation (agent-kit, NextNotifier, wee-slack)
uses the same per-channel scanning approach.

Changes:
- Cap scan depth to budget*2 per type group (~300 API calls max with
  default max_channels=50, down from ~2000+)
- Return scan metadata (channels scanned, API calls) from each type group
- Prepend an xoxp limitation note to response text so the LLM knows
  results may be partial
- Update tool description to explain xoxc vs xoxp behavior
- Fix inaccurate comment about conversations.list sort order

* fix: validate GetMutedChannels response to surface xoxp failures

Add validate() call after ParseResponse in GetMutedChannels (prefs.go),
matching the pattern used in ClientCounts. Without this, xoxp tokens
receive {ok:false, error:missing_scope} but ParseResponse only checks
HTTP status — the error was silently swallowed, returning nil/nil.

This caused muted channels to leak into xoxp results (17/28 channels
were muted in testing). Now the error propagates to the handler's
existing warn-and-proceed logic.

Also surface the limitation in the xoxp response note so the LLM
knows muted filtering is unavailable.

* fix: add shouldAddTool gating for conversations_unreads and conversations_mark

Both tools were missing Tool* constants, ValidToolNames entries, and
shouldAddTool() wrapping — breaking the established pattern used by
every other tool in the server. This prevented selective enable/disable
via the enabledTools config.

Also fixes indentation on conversations_mark registration block.
2026-02-13 14:01:09 -06:00
Manuel Kreutz 3e0e4ea792 fix: unread count persistence + backfill + muted channel filtering (#2)
* fix: use index-based range loop so unread counts persist

The message-fetch loop uses 'for _, uc := range unreadChannels', so
writes to uc.UnreadCount go to a copy — the original slice element
stays at 0. Messages are fetched correctly (appended to a separate
slice), but the unread count column is silently wrong.

Switch to 'for i := range' and index into the slice directly.
Also aligns struct field formatting (gofmt).

* feat: backfill unread counts via conversations.history

client.counts returns HasUnreads (bool) and MentionCount per channel,
but MentionCount is only non-zero for @mentions. Regular channel
activity shows as 'has unreads' with count 0.

For channels where HasUnreads=true and MentionCount=0, call
conversations.history(oldest=lastRead, limit=20) to count actual
unread messages. DMs don't need this since every message counts
as a mention.

* feat: filter muted channels from unreads by default

Fetches muted channel set from users.prefs.get (all_notifications_prefs)
and excludes them from both ClientCounts and conversations.info fallback
paths. Adds include_muted opt-in parameter to show muted channels when
explicitly requested.

Muted status is only available in users.prefs.get as a nested JSON
string — it is not exposed by client.counts, conversations.info, or
conversations.list.
2026-02-11 19:43:05 -06:00
flare576 e43380f89a fix: validate ok field in ClientCounts to trigger xoxp fallback
ClientCounts was not calling baseResponse.validate() after ParseResponse,
so when xoxp tokens received HTTP 200 with {"ok": false, "error":
"not_allowed_token_type"}, no Go error was raised and the xoxp fallback
path never triggered. Users saw "0 unreads" silently.

This follows the pattern used in search.go which correctly validates.

Credit: @140am for identifying the issue and providing the fix.
2026-02-11 17:46:12 -06:00
flare576 e238772b7e feat: add xoxp token fallback for conversations_unreads using conversations.info
Co-authored-by: niebloomj <5156403+niebloomj@users.noreply.github.com>
2026-02-11 17:11:17 -06:00
Saoud Rizwan 9cbbecb270 refactor: address PR review feedback
- Extract handler params to structs with parsing functions
  (unreadsParams, markParams) following existing patterns
- Add SLACK_MCP_MARK_TOOL env var guard for conversations_mark
  (disabled by default, requires explicit opt-in)
- Remove unused categorizeChannel and getChannelDisplayName functions
- Update README with conversations_mark safety note and env var docs
2026-02-11 17:10:48 -06:00
Saoud Rizwan f695d2c474 fix: use IsExtShared metadata instead of name prefix for partner channels
- 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.
2026-02-11 17:10:04 -06:00
Saoud Rizwan cfde97c945 feat: add mentions_only filter and conversations_mark tool
- 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
2026-02-11 17:10:03 -06:00
Saoud Rizwan 456a8c0153 fix: handle # prefix in channel names and fix partner categorization
- Strip existing # prefix before adding to avoid ##name
- Use stripped name for ext-/shared- prefix checks for partner type
2026-02-11 17:10:03 -06:00
Saoud Rizwan b1c64345c4 fix: use ClientCounts API for reliable unread detection
- Switch from ClientUserBoot to ClientCounts API
- ClientCounts returns HasUnreads boolean for all channels
- Add ClientCounts to SlackAPI interface
- Process Channels, MPIMs, and IMs separately
2026-02-11 17:10:03 -06:00
Saoud Rizwan 7fc9c7f08c feat: add conversations_unreads tool for efficient unread message retrieval
- 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 #114
2026-02-11 17:09:21 -06:00
Dmitrii Korotovskii 6ddc82863a Merge pull request #183 from Flare576/feat/startup-auth-validation
feat: add startup auth validation and workspace-isolated cache paths
2026-02-12 00:04:15 +01:00
Dmitrii Korotovskii cae899838a Merge pull request #193 from RedSlowpoke/feat/usergroups-management
feat: add user group management tools
2026-02-11 23:40:42 +01:00
flare576 7afabd5cb9 feat: add startup auth validation and workspace-isolated cache paths
- Validate Slack tokens on startup via AuthTest() - server now fails fast
  with clear error message if tokens are invalid/expired
- Namespace cache files by TeamID (e.g., T12345_users_cache.json) to prevent
  cross-workspace cache contamination when switching between Slack workspaces
- Log authenticated workspace info on startup for debugging
2026-02-10 20:00:16 -06:00
Amin Saedi 25aea2bbce test: add integration test for error recovery middleware
Verifies that the error recovery middleware correctly converts handler
errors into isError tool results instead of JSON-RPC protocol errors.
Uses mcp-go client/server wired via stdio pipes to test the full
middleware chain without external dependencies.
2026-02-09 14:20:39 -05:00
Amin Saedi ae5a6649e7 fix: return tool errors as isError results instead of JSON-RPC errors
Per the MCP specification, tool execution errors should be returned as
tool results with isError=true, not as JSON-RPC protocol errors (-32603).
This allows LLMs to see the error message and self-correct by retrying
with different parameters.

Previously, all handler errors were returned as Go errors which mcp-go
converted into JSON-RPC -32603 responses. MCP clients either crash or
silently discard these, so the LLM never sees what went wrong.

This adds a middleware that catches handler errors and converts them to
mcp.NewToolResultError() responses. The middleware is registered before
the logger and auth middlewares, so it catches all tool handler errors
regardless of which tool triggered them.
2026-02-09 12:28:37 -05:00
sspiridonov b9ffee0bc3 feat: add enabled-tools support for usergroups tools
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-09 03:53:55 +01:00
sspiridonov d5ed2c4834 Merge branch 'master' into feat/usergroups-management 2026-02-08 23:30:14 +01:00
sspiridonov 9b29f7d907 Merge origin/master into feature/enabled-tools-flag
Resolve conflict in README.md by keeping updated env var descriptions
that document the SLACK_MCP_ENABLED_TOOLS interaction.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 22:23:46 +01:00
sspiridonov 829a70065c refactor: unify shouldAddTool function and clean up comments
- Merge shouldAddTool and shouldAddWriteTool into single function
- Remove redundant comments
- Add envVarName parameter for write tools requiring explicit enablement

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 20:55:49 +01:00
sspiridonov 2ef39feae8 feat: don't register write tools unless explicitly enabled
Write tools (conversations_add_message, reactions_add, reactions_remove,
attachment_get_data) now require explicit enablement:
- If ENABLED_TOOLS explicitly includes the tool, register it
- If ENABLED_TOOLS is empty, only register if tool-specific env var is set
- If ENABLED_TOOLS excludes the tool, don't register

This resolves the conflict where ENABLED_TOOLS="" would register all tools
but SLACK_MCP_ADD_MESSAGE_TOOL="" would fail at runtime with an error.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 20:31:35 +01:00
sspiridonov 9afc063f24 feat: add user group management tools
Add five new tools for managing Slack user groups (subteams):

- usergroups_list: List all user groups in the workspace
- usergroups_me: Manage your own membership (list/join/leave)
- usergroups_create: Create a new user group
- usergroups_update: Update group metadata (name, handle, description)
- usergroups_users_update: Replace all members of a group

The usergroups_me tool provides a convenient way to join or leave
groups without needing to know the full member list. It handles
fetching current members and updating the list automatically.

Required OAuth scopes:
- usergroups:read (for list operations)
- usergroups:write (for create/update/join/leave operations)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 20:14:45 +01:00
sspiridonov d012daea6e fix: rename payload parameter to text in conversations_add_message
The parameter name was inconsistent with the error message which said
"text must be a string". Changed to use "text" as the primary parameter
name with backward compatibility for "payload".

Fixes #181

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-08 16:02:40 +01:00
derodero24 278ac36216 feat: extract text content from Block Kit structures in messages
Messages using Slack's Block Kit format (emails forwarded via Slack,
bot notifications from Grafana/Datadog, app messages) return empty
text when retrieved via conversations_history. This extracts text
from block structures so these messages are no longer empty.

Closes #186
2026-02-07 19:26:33 +09:00
sspiridonov eaf9719421 fix: correct channel allowlist/blocklist logic
The isChannelAllowedForConfig() function had inverted return logic.
When a channel was NOT found in the list, it returned the opposite
of what was intended:
- Allowlist mode incorrectly allowed unlisted channels
- Blocklist mode incorrectly blocked unlisted channels

Fixed by changing 'return !isNegated' to 'return isNegated'.
Added unit tests for isChannelAllowedForConfig().

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 00:02:04 +01:00
flare576 3c19a58e86 refactor: move token type note from tool description to README 2026-02-05 16:14:45 -06:00
flare576 f0e830e431 feat: add OAuth token fallback for users_search
For xoxp/xoxb tokens, search the local users cache using regex matching
on username, real name, display name, and email. Browser tokens (xoxc/xoxd)
continue using the edge API for real-time search.
2026-02-04 17:41:51 -06:00
flare576 a7b7e58dbd feat: add users_search tool for finding users by name/email
Adds a new users_search tool that searches for Slack users by name,
email, or display name using the Edge API. Returns user details
including UserID, username, real name, display name, email, title,
and DM channel ID (if available in cache).

Note: This feature requires browser session tokens (xoxc/xoxd),
not OAuth tokens (xoxp/xoxb), similar to conversations_unreads.
2026-02-04 17:40:28 -06:00
Dmitrii Korotovskii 09a31e6d99 Merge pull request #174 from dhpwd/fix/include-thread-broadcast-replies
fix: include thread_broadcast replies in conversations_replies
2026-02-04 20:32:41 +01:00
Dmitrii Korotovskii 993bfb4584 Merge pull request #175 from dosentmatter/fix/154-user-id-w-prefix
fix: support user id with w prefix
2026-02-04 20:31:39 +01:00
Lukasz Jagiello 503c752278 feat: add cache TTL and refresh-on-error for channel lookups
Fixes the issue where newly created Slack channels aren't discovered
until the MCP server is restarted.

Key changes:
- Add configurable cache TTL via SLACK_MCP_CACHE_TTL env var (default 1h)
- TTL uses file mtime so it survives server restarts correctly
- Add refresh-on-error: when channel lookup fails, refresh cache and retry once
- Add rate limiting via SLACK_MCP_MIN_REFRESH_INTERVAL (default 30s)
- Use atomic.Pointer[T] for cache maps to eliminate GC pressure on reads
- Thread-safe implementation with proper mutex protection

The refresh-on-error flow:
1. Channel name lookup fails (not in cache)
2. Force refresh channels from Slack API (rate-limited)
3. Retry lookup once
4. Return error if still not found

Performance optimizations:
- Cache config parsed once at init, not on every refresh
- Atomic pointer swap for map access (no copying on reads)
- Skip redundant lookup when rate-limited
- Single lock scope for rate limit check (prevents TOCTOU race)

Env vars:
- SLACK_MCP_CACHE_TTL: Cache duration (e.g., "1h", "30m", "3600", "0" to disable)
- SLACK_MCP_MIN_REFRESH_INTERVAL: Min time between forced refreshes (e.g., "30s")
2026-02-03 00:49:51 -08:00
dosentmatter 4ca5c64147 Add test for isSlackUserIDPrefix
Issue: #154
2026-02-01 20:52:52 -08:00
dosentmatter a7082b3141 fix: Support W-prefixed user IDs in filter_users_from
Fixes: #154
2026-02-01 20:50:53 -08:00
dhpwd df948cb66d fix: include thread_broadcast replies in conversations_replies
Previously, messages with SubType "thread_broadcast" (i.e., threaded
replies sent with "also send to channel") were being filtered out by
the convertMessagesFromHistory function. This caused conversations_replies
to return incomplete results when a thread contained broadcast replies.

The existing filter excluded all messages with a non-empty SubType except
"bot_message". This change adds "thread_broadcast" to the allowlist,
ensuring broadcast replies are included in thread results.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 22:05:35 +00:00
sspiridonov d06ce63995 feat: add tool name constants and validation for --enabled-tools
- Add Tool* constants to avoid magic strings in tool registration
- Add ValidToolNames slice and ValidateEnabledTools function
- Validate tool names at startup, fail with helpful error message
- Update tests to use constants

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 15:19:45 +01:00
sspiridonov cc489e8379 refactor: simplify --enabled-tools to only control registration
Simplify the logic so that:
- ENABLED_TOOLS only controls which tools are registered/exposed via MCP
- Empty ENABLED_TOOLS = all tools registered (no filtering)
- Runtime permissions (ADD_MESSAGE_TOOL, REACTION_TOOL, ATTACHMENT_TOOL)
  are always enforced in handlers regardless of ENABLED_TOOLS

This keeps the two concerns separate:
1. Registration/visibility (ENABLED_TOOLS)
2. Runtime permissions (individual tool env vars)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:50:54 +01:00
sspiridonov 1a4efadc15 add tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 13:21:45 +01:00
sspiridonov 274a156282 feat: add --enabled-tools flag to filter available MCP tools
Add a new CLI flag `--enabled-tools` (alias `-e`) and environment variable
`SLACK_MCP_ENABLED_TOOLS` to allow users to specify which MCP tools should
be loaded. This provides flexibility to limit tool exposure based on
security requirements or use case.

- If not set, all tools are enabled (backward compatible)
- Accepts comma-separated tool names
- CLI flag takes precedence over environment variable

Available tools: conversations_history, conversations_replies,
conversations_add_message, reactions_add, reactions_remove,
attachment_get_data, conversations_search_messages, channels_list

Example usage:
  slack-mcp-server --enabled-tools=conversations_history,channels_list
  SLACK_MCP_ENABLED_TOOLS=channels_list slack-mcp-server

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:09:35 +01:00
flare576 ef8fc03a85 refactor: rename files_get to attachment_get_data for Slack terminology consistency
- Tool: files_get → attachment_get_data
- Field: FileIDs → AttachmentIDs
- Env var: SLACK_MCP_FILES_TOOL → SLACK_MCP_ATTACHMENT_TOOL

Per maintainer feedback - aligns with Slack's 'attachment' terminology
and may improve LLM performance due to training data prevalence.
2026-01-29 17:18:14 -06:00
flare576 98c92caccf feat: add FileIDs field to message output
Enables agents to discover file IDs from conversation history,
completing the workflow for files_get tool usage.
2026-01-29 17:04:48 -06:00
flare576 81606854f1 feat: add files_get tool for downloading file content
Adds ability to download file content by file ID, addressing maintainer
request on PR #170.

- New files_get tool gated by SLACK_MCP_FILES_TOOL env var
- Text files (text/*, application/json, etc.) returned as plain text
- Binary files returned as base64-encoded content
- 5MB size limit to keep responses reasonable for LLM context
- Returns structured JSON: file_id, filename, mimetype, size, encoding, content
2026-01-29 16:50:05 -06:00
flare576 4ec57da9fb feat: add BotName, FileCount, HasMedia fields to message output
Adds metadata fields to help identify media-containing messages:
- BotName: populated from msg.BotProfile.Name for bot messages (e.g., 'giphy')
- FileCount: count of attached files
- HasMedia: true if message has files OR image blocks

This provides visibility into message types that was previously stripped
from the Slack API response, addressing user requests in issue #88.

For SearchMessage results, only HasMedia is populated (via blocks) since
the search API doesn't return BotProfile or Files data.
2026-01-29 16:21:30 -06:00