Repository navigation
Built-in Slack MCP integration requests full scope superset even when only read tools are exposed #4935
Description
Describe the bug
When authenticating the built-in Slack MCP server ( mcp.slack.com ) via Copilot CLI, the generated OAuth consent URL requests the full superset of Slack scopes — including write scopes ( chat:write , files:write , channels:write , canvases:write , lists:write , reactions:write , im:write , mpim:write , groups:write ) — even though the session only exposes a small set of read-only tools:
• slack_read_channel
• slack_read_thread
• slack_list_channel_members
• slack_search_public
Example generated authorize URL (redacted):
Expected behavior
Actual behavior
Users are asked to grant far broader permissions (including message sending, file uploads, channel creation, list/canvas writes) than the tool set they can actually use, violating least-privilege expectations.
Suggested fix
• Scope the OAuth request dynamically to the active/allowed tool set, or
• Provide a config option (e.g. in mcp-config.json ) to restrict which scopes are requested for the built-in Slack MCP connection.
Affected version
1.0.87-0
Steps to reproduce the behavior
- Ensure the built-in Slack MCP server ( mcp.slack.com ) is available in Copilot CLI (no custom config needed — it's a first-party integration).
- In an interactive Copilot CLI session, run /mcp (or trigger any Slack-related request) so Copilot needs to authenticate with Slack.
- Confirm only a subset of read tools are currently exposed for the session, e.g. slack_read_channel , slack_read_thread , slack_list_channel_members , slack_search_public (visible via the tool list / <tools_changed_notice> or /mcp output).
- Trigger the OAuth flow (Copilot opens/prints an authorization URL and starts a local loopback listener, e.g. http://127.0.0.1:PORT/ ).
- Inspect the generated https://slack.com/oauth/v2_user/authorize?... URL's scope= query parameter.
- Observe: the scope parameter includes the full superset of Slack scopes — including write scopes ( chat:write , files:write , channels:write , canvases:write , lists:write , reactions:write , im:write , mpim:write , groups:write ) and additional read scopes ( users:read , users:read.email , emoji:read , search:read.private/mpim/im/files/users ) — none of which are required by the 4 tools actually exposed.
Expected behavior
The requested scope list should match only the tools actually enabled/exposed for the current session (or at minimum, be configurable), rather than always requesting the full write+read superset.
Activity
nberger1 commentedon Sep 30, 2026
Confirmed on 1.0.89. Adding what happens after the consent screen.
Test setup: throwaway free workspace, internal app configured with six read-only user scopes (
search:read,channels:read,groups:read,im:read,mpim:read,users:read).Requesting the full
scopes_supportedsuperset againstoauth/v2_user/authorize:invalid_scope, no narrowing to the app's configured scopes.chat.postMessagewith that token succeeded.So an app configured read-only ends up with a token that can post as the user, permanently.
Restricting scopes on the Slack app doesn't prevent this. The
toolsallowlist inmcp-config.jsonis the only available lever and it has no effect on requested scopes.Context for the size of the superset:
tools/listonmcp.slack.comreturns 27 tools, 13 of them writes (slack_send_message,slack_schedule_message,slack_create_conversation,slack_get_file_upload_url,slack_update_canvas, ...). The scopes match the server's full surface rather than the tools exposed to the session.Deriving the scope list from the enabled tool set would fix it.
Triage note: the report describes
mcp.slack.comas a first-party built-in. On my install it isn't. Noslackstring in the bundles for 1.0.85-1.0.89, and I had to add it manually with a staticoauthClientId. May be version or plan dependent.