Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bbe2866
feat: add HTTP server entry point for OAuth support
mwbrooks Apr 1, 2026
d0e7a52
feat: add Connect/Disconnect button to App Home
mwbrooks Apr 1, 2026
79efc88
docs: add OAuth install step to README setup instructions
mwbrooks Apr 2, 2026
479c406
feat: add Slack MCP Server support when user token is available
mwbrooks Apr 2, 2026
9fe578d
Make it work
WilliamBergamin Apr 2, 2026
cbaed00
Revert "Make it work"
mwbrooks Apr 6, 2026
657d88e
feat: replace the connect/disconnect buttons with status text
mwbrooks Apr 6, 2026
abc940f
feat: fallback on bot token when oauth creds are missing
mwbrooks Apr 6, 2026
75f5f71
refactor: use /slack/install URL and remove MCP fallback logic
mwbrooks Apr 6, 2026
1a5d616
chore: remove unused connect_account action handler
mwbrooks Apr 6, 2026
1a64be3
refactor: merge Slack MCP prompt into main system prompt
mwbrooks Apr 6, 2026
0bf9caa
chore: remove install URL print on server startup
mwbrooks Apr 6, 2026
6f6dde1
docs: add MCP toggle step to OAuth setup instructions
mwbrooks Apr 6, 2026
37962fd
chore: move SLACK_SIGNING_SECRET under OAuth credentials in .env.sample
mwbrooks Apr 7, 2026
6f73709
chore: standardize ngrok placeholder to YOUR_NGROK_SUBDOMAIN
mwbrooks Apr 7, 2026
35e44fb
docs: improve readability of OAuth env setup step in README
mwbrooks Apr 7, 2026
5b59b59
fix: remove divider before @Casey context block in App Home
mwbrooks Apr 7, 2026
1d4aa94
feat: instruct Casey to look up email via Slack MCP before password r…
mwbrooks Apr 7, 2026
3704b8f
chore: use context.user_token instead of manual query
mwbrooks Apr 7, 2026
caaca55
feat: port Slack MCP Server changes to claude-agent-sdk and openai-ag…
mwbrooks Apr 7, 2026
9db55bb
bug: fix displaying the email address for password resets
mwbrooks Apr 7, 2026
c64ac1c
bug: fix context access in openai-agents-sdk emoji and resolve tools
mwbrooks Apr 7, 2026
a93888a
chore: ignore .slack/apps.json files in monorepo gitignore
mwbrooks Apr 7, 2026
5ec69b3
bug: read OAuth user scopes from manifest.json instead of hardcoding
mwbrooks Apr 7, 2026
71a44cc
fix: add pytest asyncio_mode and fix ruff formatting
mwbrooks Apr 7, 2026
f3bc921
Merge branch 'main' into slack-mcp-server
mwbrooks Apr 7, 2026
413fc75
fix: improve password reset prompt to look up email from Slack profile
mwbrooks Apr 7, 2026
bf7f95d
refactor: consolidate manifest.json and manifest_oauth.json into sing…
mwbrooks Apr 7, 2026
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@

# Git
.worktrees/

# Slack App Templates
*/.slack/apps.json
*/.slack/apps.dev.json
12 changes: 9 additions & 3 deletions claude-agent-sdk/.env.sample
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# Required, set your Anthropic API key.
ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEY

# Optional, uncomment and set when running without the Slack CLI (python3 app.py).
# SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKEN
# SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN

# Required for OAuth (app_oauth.py). Set your app's OAuth credentials.
# SLACK_CLIENT_ID=YOUR_SLACK_CLIENT_ID
# SLACK_CLIENT_SECRET=YOUR_SLACK_CLIENT_SECRET
# SLACK_REDIRECT_URI=https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/oauth_redirect
# SLACK_SIGNING_SECRET=YOUR_SLACK_SIGNING_SECRET

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

# Required, set your Anthropic API key.
ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEY
3 changes: 3 additions & 0 deletions claude-agent-sdk/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@ logs/
.pytype/
.idea/

# oauth data
data/

# claude
.claude/*.local.json
103 changes: 103 additions & 0 deletions claude-agent-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,101 @@ python3 app.py

</details>

<details><summary><strong>Using OAuth HTTP Server (with ngrok)</strong></summary>

#### OAuth HTTP Server

This mode uses an HTTP server instead of Socket Mode, which is required for OAuth-based distribution.

1. Install [ngrok](https://ngrok.com/download) and start a tunnel:

```sh
ngrok http 3000
```

2. Copy the `https://*.ngrok-free.app` URL from the ngrok output.

<details><summary><strong>Using Slack CLI</strong></summary>

#### Slack CLI

3. Update `manifest.json` for HTTP mode:
- Set `socket_mode_enabled` to `false`
- Replace `ngrok-free.app` with your ngrok domain (e.g. `YOUR_NGROK_SUBDOMAIN.ngrok-free.app`)

4. Create a new local dev app:

```sh
slack install -E local
```

5. Enable MCP for your app:
- Run `slack app settings` to open your app's settings
- Navigate to **Agents & AI Apps** in the left-side navigation
- Toggle **Model Context Protocol** on

6. Update your `.env` OAuth environment variables:
- Run `slack app settings` to open App Settings
- Copy **Client ID**, **Client Secret**, and **Signing Secret**
- Update `SLACK_REDIRECT_URI` in `.env` with your ngrok domain

```sh
SLACK_CLIENT_ID=YOUR_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
SLACK_REDIRECT_URI=https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/oauth_redirect
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET
```

7. Start the app:

```sh
slack run app_oauth.py
```

8. Click the install URL printed in the terminal to install the app to your workspace via OAuth.

</details>

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

#### Terminal

3. Create your Slack app at [api.slack.com/apps/new](https://api.slack.com/apps/new) using [`manifest.json`](./manifest.json). Before pasting the manifest, set `socket_mode_enabled` to `false` and replace `ngrok-free.app` with your ngrok domain.

4. Install the app to your workspace and copy the following values into your `.env`:
- **Signing Secret** — from _Basic Information_
- **Bot User OAuth Token** — from _OAuth & Permissions_
- **Client ID** and **Client Secret** — from _Basic Information_

```sh
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET
SLACK_BOT_TOKEN=xoxb-YOUR_BOT_TOKEN
SLACK_CLIENT_ID=YOUR_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
SLACK_REDIRECT_URI=https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/oauth_redirect
```

Replace `your-subdomain` in `SLACK_REDIRECT_URI` with your ngrok subdomain.

5. Enable MCP for your app:
- Open your app at [api.slack.com/apps](https://api.slack.com/apps)
- Navigate to **Agents & AI Apps** in the left-side navigation
- Toggle **Model Context Protocol** on

6. Start the app:

```sh
python3 app_oauth.py
```

7. Click the install URL printed in the terminal to install the app to your workspace via OAuth.

</details>

> **Note:** Each time ngrok restarts, it generates a new URL. You'll need to update the ngrok domain in `manifest.json`, `SLACK_REDIRECT_URI` in your `.env`, and re-install the app.

</details>

### Using the App

Once Casey is running, there are three ways to interact:
Expand Down Expand Up @@ -172,6 +267,14 @@ ruff format

`app.py` is the entry point for the application and is the file you'll run to start the server. This project uses `AsyncApp` from Bolt for Python, with all handlers running asynchronously.

### `app_oauth.py`

`app_oauth.py` is an alternative entry point that runs the app in HTTP mode instead of Socket Mode. This is intended for deployments that use OAuth for app distribution. See the HTTP Mode section under Development for setup instructions.

### `manifest_oauth.json`

`manifest_oauth.json` is the app manifest configured for HTTP mode (Socket Mode disabled, with request URLs for event subscriptions and interactivity). Use this when setting up the app for HTTP mode instead of `manifest.json`.

### `/listeners`

Every incoming request is routed to a "listener". This directory groups each listener based on the Slack Platform feature used.
Expand Down
36 changes: 33 additions & 3 deletions claude-agent-sdk/agent/casey.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
TextBlock,
create_sdk_mcp_server,
)
from claude_agent_sdk.types import McpHttpServerConfig

from agent.context import casey_deps_var
from agent.deps import CaseyDeps
Expand Down Expand Up @@ -56,6 +57,7 @@
2. Search the knowledge base for relevant articles
3. If the KB has a solution, walk the user through it step by step
4. If the issue requires action (password reset, ticket creation), use the appropriate tool
- For password resets: follow the instructions in the `trigger_password_reset` tool description to obtain the user's email before calling it
5. After taking action, confirm what was done and what the user should expect next
6. If you cannot resolve the issue, create a support ticket and let the user know

Expand All @@ -74,6 +76,21 @@
Call this once when the issue is fully resolved (password reset done, ticket created, problem fixed).
- Do not use `eyes` — it is added automatically

## SLACK MCP SERVER
You may have access to the Slack MCP Server, which gives you powerful Slack tools beyond \
your built-in IT helpdesk tools. Use them whenever they would help the user.

Available capabilities:
- **Search**: Search messages and files across public channels, search for channels by name
- **Read**: Read channel message history, read thread replies, read canvas documents
- **Write**: Send messages, create draft messages, schedule messages for later
- **Canvases**: Create, read, and update Slack canvas documents

Use these tools proactively when they can help resolve an IT issue — for example, \
searching for related reports from other users, checking a channel for outage updates, \
or creating a canvas to document a solution. Also use them when the user explicitly \
asks you to perform a Slack action like sending a message or creating a canvas.

## BOUNDARIES
- You are an IT helpdesk agent only — politely redirect non-IT questions
- Do not make up system statuses or ticket numbers — always use the provided tools
Expand All @@ -95,7 +112,9 @@
],
)

ALLOWED_TOOLS = [
SLACK_MCP_URL = "https://mcp.slack.com/mcp"

CASEY_TOOLS = [
"add_emoji_reaction",
"check_system_status",
"create_support_ticket",
Expand Down Expand Up @@ -124,10 +143,21 @@ async def run_casey_agent(
if deps:
casey_deps_var.set(deps)

mcp_servers: dict = {"casey-tools": casey_tools_server}
allowed_tools = list(CASEY_TOOLS)

if deps and deps.user_token:
mcp_servers["slack-mcp"] = McpHttpServerConfig(
type="http",
url=SLACK_MCP_URL,
headers={"Authorization": f"Bearer {deps.user_token}"},
)
allowed_tools.append("mcp__slack-mcp__*")

options = ClaudeAgentOptions(
system_prompt=CASEY_SYSTEM_PROMPT,
mcp_servers={"casey-tools": casey_tools_server},
allowed_tools=ALLOWED_TOOLS,
mcp_servers=mcp_servers,
allowed_tools=allowed_tools,
permission_mode="bypassPermissions",
)

Expand Down
1 change: 1 addition & 0 deletions claude-agent-sdk/agent/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ class CaseyDeps:
channel_id: str
thread_ts: str
message_ts: str
user_token: str | None = None
8 changes: 6 additions & 2 deletions claude-agent-sdk/agent/tools/password_reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
"Trigger a password reset for a specified user account. "
"Use this tool when a user requests a password reset for their own account "
"or reports being locked out. The reset link will be sent to their registered "
"email address."
"email address. "
"IMPORTANT: You need the user's email address for target_user. "
"First, try to look up the user's Slack profile to get their email address. "
"If the lookup fails or does not return an email, ask the user for their email address. "
"Never guess or assume — you must either look it up or ask for it."
),
input_schema={"target_user": str},
)
Expand All @@ -17,7 +21,7 @@ async def trigger_password_reset_tool(args):

text = (
f"Password reset initiated for **{target_user}**.\n\n"
f"A reset link has been sent to the email address on file. "
f"A reset link has been emailed to **{target_user}**. "
f"The link will expire in 30 minutes.\n\n"
f"_If the user doesn't receive the email within 5 minutes, "
f"ask them to check their spam folder or verify their registered email address._"
Expand Down
32 changes: 32 additions & 0 deletions claude-agent-sdk/app_oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import logging
import os

from dotenv import load_dotenv
from slack_bolt.async_app import AsyncApp
from slack_sdk.web.async_client import AsyncWebClient

from listeners import register_listeners
from oauth import oauth_settings

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

logging.basicConfig(level=logging.DEBUG)

app = AsyncApp(
signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
token=os.environ.get("SLACK_BOT_TOKEN"),
client=AsyncWebClient(
base_url=os.environ.get("SLACK_API_URL", "https://slack.com/api"),
token=os.environ.get("SLACK_BOT_TOKEN"),
),
# Allow bot-posted messages (e.g. issue modal submissions with metadata)
# to reach the message handler instead of being silently dropped
ignoring_self_events_enabled=False,
oauth_settings=oauth_settings,
)

register_listeners(app)

if __name__ == "__main__":
port = int(os.environ.get("PORT", 3000))
app.start(port=port)
14 changes: 13 additions & 1 deletion claude-agent-sdk/listeners/events/app_home_opened.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import os
from logging import Logger
from urllib.parse import urljoin

from slack_bolt.context.async_context import AsyncBoltContext
from slack_sdk.web.async_client import AsyncWebClient
Expand All @@ -12,7 +14,17 @@ async def handle_app_home_opened(
"""Publish the App Home view when a user opens the app's Home tab."""
try:
user_id = context.user_id
view = build_app_home_view()
install_url = None
is_connected = False

if os.environ.get("SLACK_CLIENT_ID"):
if context.user_token:
is_connected = True
else:
redirect_uri = os.environ.get("SLACK_REDIRECT_URI", "")
install_url = urljoin(redirect_uri, "/slack/install")
Comment on lines +24 to +25
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 💯 I like this better then what I did before


view = build_app_home_view(install_url=install_url, is_connected=is_connected)
await client.views_publish(user_id=user_id, view=view)
except Exception as e:
logger.exception(f"Failed to publish App Home: {e}")
1 change: 1 addition & 0 deletions claude-agent-sdk/listeners/events/app_mentioned.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ async def handle_app_mentioned(
channel_id=channel_id,
thread_ts=thread_ts,
message_ts=event["ts"],
user_token=context.user_token,
)
response_text, new_session_id = await run_casey_agent(
cleaned_text, session_id=existing_session_id, deps=deps
Expand Down
1 change: 1 addition & 0 deletions claude-agent-sdk/listeners/events/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ async def handle_message(
channel_id=channel_id,
thread_ts=thread_ts,
message_ts=event["ts"],
user_token=context.user_token,
)
response_text, new_session_id = await run_casey_agent(
text, session_id=existing_session_id, deps=deps
Expand Down
Loading
Loading