Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 33 additions & 25 deletions src/gete/catalog/connections/slack-mcp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,20 @@ oauth:
# answers with the user's token at the top level and nothing else to take.
authorization_url: https://slack.com/oauth/v2_user/authorize
# The metadata names client_secret_post as the only client authentication
# method. Whether Gemini Enterprise sends the secret that way is
# unconfirmed, and a connection cannot say: the Authorization resource has
# no field for it.
# method, and the code exchange goes through however Gemini Enterprise
# sends the secret. Which of the two it sends a connection cannot say: the
# Authorization resource has no field for it.
token_url: https://slack.com/api/oauth.v2.user.access
# scope_parameter stays at its default. The app pair needs user_scope
# because oauth/v2/authorize reads scope as the app's own permissions. This
# endpoint is reached by standard MCP clients as an RFC 8414 authorization
# server, and those send a plain scope parameter, so scope it is -
# unconfirmed; user_scope is the switch if the consent screen answers
# invalid_scope.
# because oauth/v2/authorize reads scope as the app's own permissions; this
# endpoint is reached as an RFC 8414 authorization server, where a plain
# scope parameter is what a standard MCP client sends, and the consent
# screen was built from the scopes sent that way.
# pkce stays unset: S256 is supported here, not required.
# No authorization_query either. One would keep access_type and prompt off
# the URL, as notion-mcp does, but it would also fix the scopes, and the
# menu below could not be offered beside it. Slack is expected to ignore
# parameters it does not know; unconfirmed.
# menu below could not be offered beside it. Slack ignores the two: the
# consent screen was reached with both on the URL.
#
# Slack fixes a user token's scopes at consent. A scope added here later
# reaches nobody already authorized until their authorization is reset, so
Expand Down Expand Up @@ -90,27 +89,36 @@ setup: |
URLs): the one named above. Put the app's client id and client secret in
the secrets named above.

Turn Model Context Protocol on, under Features -> Agents & AI Apps. With it
off the server answers the initialize request with 400 and "App is not
enabled", pointing at the app's own settings page. That is a refusal made
after reading the token, not a refusal of it - a token the server does not
accept answers 401 invalid_token instead - so a correct token looks broken
until the toggle is on. Turning it on needs neither a reinstall nor a fresh
authorization: a token taken before the toggle works after it.

Where the workspace requires apps to be approved, an admin has to approve
this one before anyone can authorize. Semantic search needs a plan that
includes Slack AI search; keyword search works on any plan.

No authorization has been taken through Gemini Enterprise yet, so the
following is read off the server's metadata rather than seen to work:
- The token endpoint names client_secret_post as its only client
authentication method. Whether Gemini Enterprise sends the client secret
that way is unconfirmed.
- The scopes are sent under scope, the parameter a standard MCP client
uses. If the consent screen answers invalid_scope, set
oauth.scope_parameter to user_scope in gete.yaml.
- The authorization URL carries access_type and prompt, which are
Google's vocabulary. Whether Slack ignores them is unconfirmed.
- A standard MCP client sends resource (RFC 8707) with the code exchange;
Gemini Enterprise does not. Whether Slack requires it is unconfirmed.
An authorization taken through Gemini Enterprise settles what the server's
metadata alone could not:
- The code exchange goes through however Gemini Enterprise sends the client
secret, though the token endpoint names client_secret_post as its only
client authentication method.
- The consent screen is built from the scopes sent under scope, the
parameter a standard MCP client uses; user_scope, which the app pair
needs, is not wanted here.
- access_type and prompt on the authorization URL are ignored.
- resource (RFC 8707) is not required. A standard MCP client sends it with
the code exchange and Gemini Enterprise does not; the exchange goes
through either way.
- The metadata offers the refresh_token grant, so tokens may expire.
Whether Gemini Enterprise refreshes them is its own doing.
Whether Gemini Enterprise refreshes one is unconfirmed: none issued here
has yet lived long enough to find out.

# verified is absent because no authorization has been taken through Gemini
# Enterprise yet.
verified:
gemini_enterprise: 2026-09-09

examples:
accepts:
Expand Down
29 changes: 24 additions & 5 deletions tests/conformance/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,17 +285,36 @@ def test_slack_mcp_takes_user_tokens_and_refuses_bot_tokens() -> None:


def test_slack_mcp_says_what_a_person_has_to_do_before_authorizing() -> None:
"""The app's user scopes, the one redirect URI, and what has not been seen
to work yet."""
"""The app's user scopes, the one redirect URI, and the toggle the server
reads before it will hold a session."""
setup = CATALOG["slack-mcp"]["setup"]
assert "User Token Scopes" in setup
assert "Bot Token Scopes" in setup
assert "redirect URI" in setup
assert "unconfirmed" in setup
assert "Model Context Protocol" in setup


def test_slack_mcp_is_not_verified_until_an_authorization_has_been_taken() -> None:
assert "verified" not in CATALOG["slack-mcp"]
def test_slack_mcp_names_the_toggle_that_leaves_a_good_token_refused() -> None:
"""With Model Context Protocol off the server refuses the initialize
request, which reads like a bad token and is not one. Turning it on takes
back nothing already granted, so setup must say that too."""
setup = CATALOG["slack-mcp"]["setup"]
assert "Agents & AI Apps" in setup
assert "400" in setup
assert "reinstall" in setup


def test_slack_mcp_leaves_only_refreshing_unconfirmed() -> None:
"""The rest of setup was seen through Gemini Enterprise; no token issued
there has yet lived long enough to expire."""
setup = CATALOG["slack-mcp"]["setup"]
unconfirmed = [line for line in setup.splitlines() if "unconfirmed" in line]
assert len(unconfirmed) == 1
assert "refresh" in unconfirmed[0]


def test_slack_mcp_records_the_authorization_it_was_verified_with() -> None:
assert CATALOG["slack-mcp"]["verified"] == {"gemini_enterprise": "2026-09-09"}


def test_slack_mcp_can_sit_beside_a_connection_accepted_by_elimination() -> None:
Expand Down