Skip to content

Commit caaca55

Browse files
committed
feat: port Slack MCP Server changes to claude-agent-sdk and openai-agents-sdk
Port all pydantic-ai branch changes to the other two implementations: - Add Slack MCP Server integration with OAuth user token - Replace button-based App Home UI with text-based MCP status - Use AsyncOAuthSettings for claude-agent-sdk's AsyncApp - Add MCP setup steps to README OAuth sections - Expand user scopes for MCP capabilities - Update system prompt with Slack MCP Server section - Simplify app_home_opened to use context.user_token
1 parent 3704b8f commit caaca55

24 files changed

Lines changed: 392 additions & 308 deletions

claude-agent-sdk/.env.sample

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEY
55
# SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKEN
66
# SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN
77

8-
# Optional, uncomment and set when running app_oauth.py without the Slack CLI.
9-
# SLACK_SIGNING_SECRET=YOUR_SLACK_SIGNING_SECRET
10-
118
# Required for OAuth (app_oauth.py). Set your app's OAuth credentials.
129
# SLACK_CLIENT_ID=YOUR_SLACK_CLIENT_ID
1310
# SLACK_CLIENT_SECRET=YOUR_SLACK_CLIENT_SECRET
14-
# SLACK_REDIRECT_URI=https://YOUR_NGROK_URL.ngrok-free.app/slack/oauth_redirect
11+
# SLACK_REDIRECT_URI=https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/oauth_redirect
12+
# SLACK_SIGNING_SECRET=YOUR_SLACK_SIGNING_SECRET
1513

1614
# Optional, uncomment and set when using a custom Slack instance.
1715
# SLACK_API_URL=YOUR_SLACK_API_URL

claude-agent-sdk/README.md

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,40 +165,46 @@ mv manifest.json manifest_socket_mode.json
165165
mv manifest_oauth.json manifest.json
166166
```
167167

168-
Replace all instances of `https://PLACEHOLDER.ngrok-free.app` in `manifest.json` with your ngrok URL.
168+
Replace all instances of `https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app` in `manifest.json` with your ngrok URL.
169169

170170
4. Create a new local dev app:
171171

172172
```sh
173173
slack install -E local
174174
```
175175

176-
5. Copy the following values into your `.env`. Run `slack app settings` and copy the **Signing Secret**, **Client ID**, and **Client Secret**:
176+
5. Enable MCP for your app:
177+
- Run `slack app settings` to open your app's settings
178+
- Navigate to **Agents & AI Apps** in the left-side navigation
179+
- Toggle **Model Context Protocol** on
180+
181+
6. Update your `.env` OAuth environment variables:
182+
- Run `slack app settings` to open App Settings
183+
- Copy **Client ID**, **Client Secret**, and **Signing Secret**
184+
- Update the **Slack Redirect URI** with your ngrok URL
177185

178186
```sh
179-
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET
180187
SLACK_CLIENT_ID=YOUR_CLIENT_ID
181188
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
182-
SLACK_REDIRECT_URI=https://YOUR_NGROK_URL.ngrok-free.app/slack/oauth_redirect
189+
SLACK_REDIRECT_URI=https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/oauth_redirect
190+
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET
183191
```
184192

185-
Replace `YOUR_NGROK_URL` in `SLACK_REDIRECT_URI` with your ngrok subdomain.
186-
187-
6. Start the app:
193+
7. Start the app:
188194

189195
```sh
190196
slack run app_oauth.py
191197
```
192198

193-
7. Click the install URL printed in the terminal to install the app to your workspace via OAuth.
199+
8. Click the install URL printed in the terminal to install the app to your workspace via OAuth.
194200

195201
</details>
196202

197203
<details><summary><strong>Using the Terminal</strong></summary>
198204

199205
#### Terminal
200206

201-
3. Create your Slack app at [api.slack.com/apps/new](https://api.slack.com/apps/new) using [`manifest_oauth.json`](./manifest_oauth.json). Before pasting the manifest, replace all instances of `https://PLACEHOLDER.ngrok-free.app` with your ngrok URL.
207+
3. Create your Slack app at [api.slack.com/apps/new](https://api.slack.com/apps/new) using [`manifest_oauth.json`](./manifest_oauth.json). Before pasting the manifest, replace all instances of `https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app` with your ngrok URL.
202208

203209
4. Install the app to your workspace and copy the following values into your `.env`:
204210
- **Signing Secret** — from _Basic Information_
@@ -210,18 +216,23 @@ SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET
210216
SLACK_BOT_TOKEN=xoxb-YOUR_BOT_TOKEN
211217
SLACK_CLIENT_ID=YOUR_CLIENT_ID
212218
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
213-
SLACK_REDIRECT_URI=https://YOUR_NGROK_URL.ngrok-free.app/slack/oauth_redirect
219+
SLACK_REDIRECT_URI=https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/oauth_redirect
214220
```
215221

216-
Replace `YOUR_NGROK_URL` in `SLACK_REDIRECT_URI` with your ngrok subdomain.
222+
Replace `YOUR_NGROK_SUBDOMAIN` in `SLACK_REDIRECT_URI` with your ngrok subdomain.
223+
224+
5. Enable MCP for your app:
225+
- Open your app at [api.slack.com/apps](https://api.slack.com/apps)
226+
- Navigate to **Agents & AI Apps** in the left-side navigation
227+
- Toggle **Model Context Protocol** on
217228

218-
5. Start the app:
229+
6. Start the app:
219230

220231
```sh
221232
python3 app_oauth.py
222233
```
223234

224-
6. Click the install URL printed in the terminal to install the app to your workspace via OAuth.
235+
7. Click the install URL printed in the terminal to install the app to your workspace via OAuth.
225236

226237
</details>
227238

claude-agent-sdk/agent/casey.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
2. Search the knowledge base for relevant articles
5858
3. If the KB has a solution, walk the user through it step by step
5959
4. If the issue requires action (password reset, ticket creation), use the appropriate tool
60+
- For password resets: look up the user's email address using Slack MCP tools before \
61+
triggering the reset — don't ask the user for information you can look up yourself
6062
5. After taking action, confirm what was done and what the user should expect next
6163
6. If you cannot resolve the issue, create a support ticket and let the user know
6264
@@ -75,6 +77,21 @@
7577
Call this once when the issue is fully resolved (password reset done, ticket created, problem fixed).
7678
- Do not use `eyes` — it is added automatically
7779
80+
## SLACK MCP SERVER
81+
You may have access to the Slack MCP Server, which gives you powerful Slack tools beyond \
82+
your built-in IT helpdesk tools. Use them whenever they would help the user.
83+
84+
Available capabilities:
85+
- **Search**: Search messages and files across public channels, search for channels by name
86+
- **Read**: Read channel message history, read thread replies, read canvas documents
87+
- **Write**: Send messages, create draft messages, schedule messages for later
88+
- **Canvases**: Create, read, and update Slack canvas documents
89+
90+
Use these tools proactively when they can help resolve an IT issue — for example, \
91+
searching for related reports from other users, checking a channel for outage updates, \
92+
or creating a canvas to document a solution. Also use them when the user explicitly \
93+
asks you to perform a Slack action like sending a message or creating a canvas.
94+
7895
## BOUNDARIES
7996
- You are an IT helpdesk agent only — politely redirect non-IT questions
8097
- Do not make up system statuses or ticket numbers — always use the provided tools

claude-agent-sdk/agent/tools/password_reset.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"Trigger a password reset for a specified user account. "
88
"Use this tool when a user requests a password reset for their own account "
99
"or reports being locked out. The reset link will be sent to their registered "
10-
"email address."
10+
"email address. Before calling this tool, look up the user's email address "
11+
"using available Slack tools if possible, rather than asking the user for it."
1112
),
1213
input_schema={"target_user": str},
1314
)
@@ -17,7 +18,7 @@ async def trigger_password_reset_tool(args):
1718

1819
text = (
1920
f"Password reset initiated for **{target_user}**.\n\n"
20-
f"A reset link has been sent to the email address on file. "
21+
f"A reset link has been sent to **{target_user}@acme.com**. "
2122
f"The link will expire in 30 minutes.\n\n"
2223
f"_If the user doesn't receive the email within 5 minutes, "
2324
f"ask them to check their spam folder or verify their registered email address._"

claude-agent-sdk/app_oauth.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33

44
from dotenv import load_dotenv
55
from slack_bolt.async_app import AsyncApp
6-
from slack_bolt.oauth.oauth_settings import OAuthSettings
76
from slack_sdk.web.async_client import AsyncWebClient
87

98
from listeners import register_listeners
10-
from oauth import BOT_SCOPES, USER_SCOPES, installation_store, state_store
9+
from oauth import oauth_settings
1110

1211
load_dotenv(dotenv_path=".env", override=False)
1312

@@ -23,19 +22,11 @@
2322
# Allow bot-posted messages (e.g. issue modal submissions with metadata)
2423
# to reach the message handler instead of being silently dropped
2524
ignoring_self_events_enabled=False,
26-
oauth_settings=OAuthSettings(
27-
client_id=os.environ.get("SLACK_CLIENT_ID"),
28-
client_secret=os.environ.get("SLACK_CLIENT_SECRET"),
29-
scopes=BOT_SCOPES,
30-
user_scopes=USER_SCOPES,
31-
installation_store=installation_store,
32-
state_store=state_store,
33-
),
25+
oauth_settings=oauth_settings,
3426
)
3527

3628
register_listeners(app)
3729

3830
if __name__ == "__main__":
3931
port = int(os.environ.get("PORT", 3000))
40-
print(f"To install the app, navigate to http://localhost:{port}/slack/install")
4132
app.start(port=port)

claude-agent-sdk/listeners/actions/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22

33
from slack_bolt.async_app import AsyncApp
44

5-
from .account_connection import handle_connect_account, handle_disconnect_account
65
from .issue_buttons import handle_issue_button
76
from .feedback_buttons import handle_feedback_button
87

98

109
def register(app: AsyncApp):
1110
app.action(re.compile(r"^category_"))(handle_issue_button)
1211
app.action("feedback")(handle_feedback_button)
13-
app.action("connect_account")(handle_connect_account)
14-
app.action("disconnect_account")(handle_disconnect_account)

claude-agent-sdk/listeners/actions/account_connection.py

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from logging import Logger
3+
from urllib.parse import urljoin
34

45
from slack_bolt.context.async_context import AsyncBoltContext
56
from slack_sdk.web.async_client import AsyncWebClient
@@ -13,26 +14,17 @@ async def handle_app_home_opened(
1314
"""Publish the App Home view when a user opens the app's Home tab."""
1415
try:
1516
user_id = context.user_id
16-
authorize_url = None
17+
install_url = None
1718
is_connected = False
1819

1920
if os.environ.get("SLACK_CLIENT_ID"):
20-
from oauth import authorize_url_generator, installation_store, state_store
21-
22-
installation = installation_store.find_installation(
23-
enterprise_id=context.enterprise_id or "",
24-
team_id=context.team_id or "",
25-
user_id=user_id,
26-
)
27-
if installation and installation.user_token:
21+
if context.user_token:
2822
is_connected = True
2923
else:
30-
state = state_store.issue()
31-
authorize_url = authorize_url_generator.generate(state)
24+
redirect_uri = os.environ.get("SLACK_REDIRECT_URI", "")
25+
install_url = urljoin(redirect_uri, "/slack/install")
3226

33-
view = build_app_home_view(
34-
authorize_url=authorize_url, is_connected=is_connected
35-
)
27+
view = build_app_home_view(install_url=install_url, is_connected=is_connected)
3628
await client.views_publish(user_id=user_id, view=view)
3729
except Exception as e:
3830
logger.exception(f"Failed to publish App Home: {e}")

claude-agent-sdk/listeners/views/app_home_builder.py

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,15 @@
2828

2929

3030
def build_app_home_view(
31-
authorize_url: str | None = None, is_connected: bool = False
31+
install_url: str | None = None, is_connected: bool = False
3232
) -> dict:
3333
"""Build the App Home Block Kit view with category buttons.
3434
3535
Args:
36-
authorize_url: OAuth authorize URL. When provided, the user has not
37-
connected and will see a "Connect" URL button.
38-
is_connected: When ``True``, the user is connected and will see a
39-
"Disconnect" action button. When both params are default, the
40-
OAuth section is hidden (app.py mode).
36+
install_url: OAuth install URL. When provided, the user has not
37+
connected and will see a link to install.
38+
is_connected: When ``True``, the user is connected and the MCP
39+
status section shows as connected.
4140
"""
4241
blocks = [
4342
{
@@ -75,7 +74,6 @@ def build_app_home_view(
7574
for cat in CATEGORIES
7675
],
7776
},
78-
{"type": "divider"},
7977
{
8078
"type": "context",
8179
"elements": [
@@ -94,40 +92,42 @@ def build_app_home_view(
9492
"type": "section",
9593
"text": {
9694
"type": "mrkdwn",
97-
"text": (
98-
":white_check_mark: *Your Slack account is connected.*\n"
99-
"Casey has access to search messages, read channels, and more."
100-
),
101-
},
102-
"accessory": {
103-
"type": "button",
104-
"text": {"type": "plain_text", "text": "Disconnect"},
105-
"action_id": "disconnect_account",
106-
"style": "danger",
95+
"text": "\U0001f7e2 *Slack MCP Server is connected.*",
10796
},
10897
}
10998
)
110-
elif authorize_url:
99+
blocks.append(
100+
{
101+
"type": "context",
102+
"elements": [
103+
{
104+
"type": "mrkdwn",
105+
"text": "Casey has access to search messages, read channels, and more.",
106+
}
107+
],
108+
}
109+
)
110+
elif install_url:
111111
blocks.append(
112112
{
113113
"type": "section",
114114
"text": {
115115
"type": "mrkdwn",
116-
"text": (
117-
":electric_plug: *Connect your Slack account*\n"
118-
"Unlock full functionality including searching messages, "
119-
"reading channels, and more."
120-
),
121-
},
122-
"accessory": {
123-
"type": "button",
124-
"text": {"type": "plain_text", "text": "Connect"},
125-
"url": authorize_url,
126-
"action_id": "connect_account",
127-
"style": "primary",
116+
"text": f"\U0001f534 *Slack MCP Server is disconnected.* <{install_url}|Connect the Slack MCP Server.>",
128117
},
129118
}
130119
)
120+
blocks.append(
121+
{
122+
"type": "context",
123+
"elements": [
124+
{
125+
"type": "mrkdwn",
126+
"text": "The Slack MCP Server enables Casey to search messages, read channels, and more.",
127+
}
128+
],
129+
}
130+
)
131131

132132
return {
133133
"type": "home",

0 commit comments

Comments
 (0)