Skip to content

Commit 43f0e1d

Browse files
CalebBarnesMastra Code (anthropic/claude-opus-4-5)abhiaiyer91
authored
Slack app provisioning and channel management (#15876)
## Summary Adds a platform channels framework to `@mastra/core` and a first implementation with `@mastra/slack` for automated Slack app provisioning. Agents can be connected to Slack workspaces with a single `connect()` call — the system handles app creation, OAuth, manifest management, and message routing. ## New Packages ### `@mastra/slack` - Automated Slack app creation via the Manifest API - Full OAuth flow for workspace installation with redirect support - Manifest drift detection and self-healing on startup - Encrypted credential storage (AES-256-GCM with HKDF-SHA256 key derivation, algorithm-prefixed ciphertext format) - Automatic config token rotation (access tokens expire every 12h, refresh tokens are single-use) with shared-promise deduplication to prevent race conditions - Threaded conversation handling via Chat SDK adapters - Slash command support with async response delivery - Agent name and description synced to Slack app metadata ## Core Changes ### `@mastra/core` - `ChannelProvider` interface — base contract for platform integrations (OAuth, webhooks, connect/disconnect lifecycle) - `ChannelsStorage` domain with `InMemory` implementation for installation and config persistence - Channel table constants (`TABLE_CHANNEL_INSTALLATIONS`, `TABLE_CHANNEL_CONFIG`) registered in `TABLE_NAMES` and `TABLE_SCHEMAS` - `ChannelConnectResult` discriminated union supporting `oauth`, `deep_link`, and `immediate` connection flows - `Mastra` class: `channels` config, `getChannelProvider()`, `getChannelProviders()`, and `channels` getter - `Agent` class: `setChannels()` / `getChannels()` for platform-managed `AgentChannels` injection - `AgentChannels`: `handleWebhookEvent` returns 503 on init failure, `__registerAdapter` records `managesRoutes` idempotently - `channels` added to `coreFeatures` for server-side feature gating ### `@mastra/server` - Channel API routes: `GET /channels/platforms`, `GET /channels/:platform/installations`, `POST /channels/:platform/connect`, `POST /channels/:platform/:agentId/disconnect` - Feature-gated with `assertChannelsAvailable()` (returns 501 if disabled) - Agent existence validated via `getAgentById()` in disconnect handler ### `@mastra/client-js` - `Channels` resource with `listPlatforms()`, `listInstallations()`, `connect()`, `disconnect()` - Typed `ChannelConnectResult` union exported for consumers ### Storage Backends - `@mastra/libsql`: `ChannelsLibSQL` domain using `LibSQLDB` wrapper with `createTable()` and core `TABLE_SCHEMAS` - `@mastra/pg`: `ChannelsPG` domain using `PgDB` wrapper with `createTable()`, `generateTableSQL()`, and `generateIndexSQL()`; includes `getExportDDL()` and registration in `ALL_DOMAINS` - `@mastra/clickhouse`: Channel tables added to `TABLE_ENGINES` with `ReplacingMergeTree()` - `@mastra/cloudflare`: Channel table types added to `RecordTypes` - Both PG and LibSQL enforce `UNIQUE INDEX` on `webhookId` for webhook routing correctness ### Playground - "Channels" tab in agent sidebar showing connected platforms - OAuth flow with same-tab redirect and automatic UI refresh - Idempotent connect button (reuses pending installations) - Popup blocker detection for deep link flows - Remove button uses `type="button"` to prevent form submission ## Example Usage ```typescript import { Mastra } from '@mastra/core/mastra'; import { SlackProvider } from '@mastra/slack'; const mastra = new Mastra({ channels: { slack: new SlackProvider({ token: process.env.SLACK_APP_CONFIG_TOKEN!, refreshToken: process.env.SLACK_APP_CONFIG_REFRESH_TOKEN!, }), }, agents: { myAgent }, }); // Connect an agent to a Slack workspace const result = await mastra.channels.slack.connect('my-agent'); // result.type === 'oauth' → redirect user to result.authorizationUrl ``` ## Test Plan - [x] `@mastra/slack` unit tests pass (59 tests: crypto, manifest, client) - [x] Core channel + storage tests pass (598 tests) - [x] Server adapter tests pass (2081 tests) - [x] All packages build successfully (ESM/CJS/DTS) — full monorepo 124/124 - [x] Manual end-to-end testing with live Slack workspace - [x] OAuth round-trip verified (connect → authorize → callback → active) - [x] Threaded message replies verified - [x] Slash commands verified - [x] Manifest drift detection and self-healing verified - [x] CI: Build, Lint, Analyze, CodeQL, Socket Security passing ## Changesets - `@mastra/client-js`: minor (Channels resource) - `@mastra/slack`: minor (new package) - `@mastra/core`: minor (platform channels framework) - `@mastra/server`, `@mastra/libsql`, `@mastra/pg`: patch (channel support) --------- Co-authored-by: CalebBarnes <CalebBarnes@users.noreply.github.com> Co-authored-by: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai> Co-authored-by: Abhi Aiyer <abhiaiyer91@gmail.com>
1 parent b2deb29 commit 43f0e1d

70 files changed

Lines changed: 5567 additions & 173 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/pre.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@
132132
"@mastra/s3": "0.5.0",
133133
"@mastra/vercel": "0.1.0",
134134
"@mastra/google-drive": "0.0.1",
135-
"@mastra/perplexity": "0.1.0-alpha.0"
135+
"@mastra/perplexity": "0.1.0-alpha.0",
136+
"@mastra/slack": "1.0.2"
136137
},
137138
"changesets": [
138139
"@mastra_deployer-15211-dependencies",

.changeset/sharp-eels-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@mastra/client-js': minor
3+
---
4+
5+
Added Channels resource to MastraClient with listPlatforms, listInstallations, connect, and disconnect methods for managing platform channel integrations.

.changeset/tangy-rings-sit.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@mastra/slack': minor
3+
---
4+
5+
Added @mastra/slack channel integration for connecting AI agents to Slack workspaces. Provides automatic Slack app provisioning via OAuth, manifest management with drift detection, encrypted credential storage, slash command support, and threaded conversation handling. Usage:
6+
7+
```ts
8+
import { SlackProvider } from '@mastra/slack';
9+
10+
const mastra = new Mastra({
11+
channels: {
12+
slack: new SlackProvider({
13+
refreshToken: process.env.SLACK_APP_CONFIG_REFRESH_TOKEN!,
14+
}),
15+
},
16+
});
17+
18+
// Connect an agent to Slack
19+
const result = await mastra.channels.slack.connect('my-agent');
20+
// result.type === 'oauth' → redirect user to result.authorizationUrl
21+
```

.changeset/tasty-comics-decide.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@mastra/core': minor
3+
'@mastra/server': patch
4+
'@mastra/libsql': patch
5+
'@mastra/pg': patch
6+
---
7+
8+
Added platform channels framework with ChannelProvider interface, ChannelsStorage domain, and ChannelConnectResult discriminated union supporting OAuth, deep link, and immediate connection flows. Channels can be registered on the Mastra instance and expose connect/disconnect/list APIs for platform integrations.

channels/slack/README.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# @mastra/slack
2+
3+
Slack integration for Mastra agents. Handles app creation, OAuth, slash commands, and messaging.
4+
5+
## Quick Start
6+
7+
```ts
8+
import { Mastra } from '@mastra/core/mastra';
9+
import { Agent } from '@mastra/core/agent';
10+
import { SlackProvider } from '@mastra/slack';
11+
12+
const myAgent = new Agent({
13+
id: 'my-agent',
14+
name: 'My Agent',
15+
model: 'openai/gpt-4.1',
16+
instructions: 'You are a helpful assistant.',
17+
});
18+
19+
const slack = new SlackProvider({
20+
refreshToken: process.env.SLACK_APP_CONFIG_REFRESH_TOKEN,
21+
// For local dev, set SLACK_BASE_URL to your tunnel URL
22+
// In production, this is auto-derived from server config
23+
baseUrl: process.env.SLACK_BASE_URL,
24+
});
25+
26+
const mastra = new Mastra({
27+
agents: { myAgent },
28+
channels: { slack },
29+
});
30+
31+
// Or configure credentials later (e.g., from UI or vault)
32+
// slack.configure({ refreshToken: 'xoxe-1-...' });
33+
34+
// Connect an agent to Slack (creates app, returns OAuth URL)
35+
const { authorizationUrl } = await slack.connect('my-agent', {
36+
name: 'My Bot',
37+
description: 'An AI assistant',
38+
iconUrl: 'https://example.com/my-bot-icon.png',
39+
slashCommands: [
40+
{ command: '/ask', prompt: 'Answer: {{text}}' },
41+
{ command: '/help', prompt: 'List your capabilities.' },
42+
],
43+
});
44+
```
45+
46+
## Setup
47+
48+
1. **Get App Configuration Tokens** from https://api.slack.com/apps (look for "Your App Configuration Tokens" section)
49+
50+
2. **Set up a tunnel** for local development:
51+
52+
```bash
53+
cloudflared tunnel --url http://localhost:4111
54+
```
55+
56+
3. **Add to .env**:
57+
```
58+
SLACK_APP_CONFIG_TOKEN=xoxe.xoxp-...
59+
SLACK_APP_CONFIG_REFRESH_TOKEN=xoxe-1-...
60+
SLACK_BASE_URL=https://abc123.trycloudflare.com
61+
```
62+
63+
> ⚠️ **Token Rotation**: Slack config access tokens expire after 12 hours, but the refresh token does not expire (it's single-use — each rotation returns a new pair). Tokens auto-rotate and are persisted to storage, so the `.env` values are only used as the initial seed. If you lose your persisted storage (e.g., DB wipe), you'll need fresh tokens from the Slack dashboard.
64+
65+
## Storage & Persistence
66+
67+
`SlackProvider` automatically uses Mastra's storage if configured. Just add `storage` to your Mastra config:
68+
69+
```ts
70+
import { LibSQLStore } from '@mastra/libsql';
71+
72+
const mastra = new Mastra({
73+
agents: { myAgent },
74+
storage: new LibSQLStore({ url: 'file:./mastra.db' }),
75+
channels: {
76+
slack: new SlackProvider({
77+
refreshToken: process.env.SLACK_APP_CONFIG_REFRESH_TOKEN!,
78+
}),
79+
},
80+
});
81+
```
82+
83+
When Mastra has storage configured, `SlackProvider` automatically:
84+
85+
- Persists rotated config tokens (so you don't need fresh tokens after restart)
86+
- Persists Slack app installations
87+
- Detects config changes (e.g., agent renames) and updates manifests on startup
88+
89+
Without storage, data is lost on restart and apps are recreated.
90+
91+
## How It Works
92+
93+
1. Register a `SlackProvider` on your `Mastra` instance
94+
2. Call `slack.connect(agentId)` to provision a Slack app and get an OAuth URL
95+
3. Visit the OAuth URL to install the app to your Slack workspace
96+
4. After installation, messages and slash commands route to your agent
97+
5. Config access tokens auto-rotate (they expire every 12 hours) and are saved to storage
98+
99+
## Slash Commands
100+
101+
Commands use prompt templates with variable substitution:
102+
103+
```ts
104+
await slack.connect('my-agent', {
105+
slashCommands: [
106+
{
107+
command: '/ask',
108+
description: 'Ask the AI a question',
109+
prompt: 'Answer this question: {{text}}',
110+
},
111+
{
112+
command: '/summarize',
113+
description: 'Summarize content',
114+
prompt: 'Summarize the following in 2-3 sentences: {{text}}',
115+
},
116+
],
117+
});
118+
```
119+
120+
Available variables: `{{text}}`, `{{userId}}`, `{{channelId}}`, `{{teamId}}`
121+
122+
## App Icons
123+
124+
Each agent's Slack app can have its own icon:
125+
126+
```ts
127+
await slack.connect('my-agent', {
128+
iconUrl: 'https://example.com/my-bot-avatar.png',
129+
});
130+
```
131+
132+
The image should be:
133+
134+
- Square (1:1 aspect ratio)
135+
- At least 512x512 pixels
136+
- PNG, JPG, or GIF format
137+
138+
The icon is uploaded automatically when the Slack app is created.
139+
140+
## Disconnecting
141+
142+
```ts
143+
await slack.disconnect('my-agent');
144+
```
145+
146+
This deletes the Slack app and removes the local installation record.

channels/slack/package.json

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"name": "@mastra/slack",
3+
"version": "1.0.2",
4+
"description": "Slack integration for Mastra agents with app factory, OAuth, and slash commands",
5+
"license": "Apache-2.0",
6+
"type": "module",
7+
"main": "dist/index.js",
8+
"types": "dist/index.d.ts",
9+
"files": [
10+
"dist",
11+
"CHANGELOG.md"
12+
],
13+
"exports": {
14+
".": {
15+
"import": {
16+
"types": "./dist/index.d.ts",
17+
"default": "./dist/index.js"
18+
},
19+
"require": {
20+
"types": "./dist/index.d.ts",
21+
"default": "./dist/index.cjs"
22+
}
23+
}
24+
},
25+
"scripts": {
26+
"build": "tsup",
27+
"dev": "tsup --watch",
28+
"typecheck": "tsc --noEmit",
29+
"test": "vitest run",
30+
"test:watch": "vitest"
31+
},
32+
"dependencies": {
33+
"@chat-adapter/slack": "^4.26.0"
34+
},
35+
"devDependencies": {
36+
"@mastra/core": "workspace:*",
37+
"@types/node": "^22.14.0",
38+
"tsup": "^8.4.0",
39+
"typescript": "^5.8.2",
40+
"vitest": "^3.1.1",
41+
"zod": "^3.23.0"
42+
},
43+
"peerDependencies": {
44+
"@mastra/core": "^1.0.0"
45+
},
46+
"publishConfig": {
47+
"access": "public"
48+
},
49+
"keywords": [
50+
"mastra",
51+
"slack",
52+
"chatbot",
53+
"ai",
54+
"agent"
55+
],
56+
"repository": {
57+
"type": "git",
58+
"url": "git+https://github.com/mastra-ai/mastra.git",
59+
"directory": "channels/slack"
60+
},
61+
"homepage": "https://mastra.ai",
62+
"bugs": {
63+
"url": "https://github.com/mastra-ai/mastra/issues"
64+
},
65+
"engines": {
66+
"node": ">=22.13.0"
67+
}
68+
}

0 commit comments

Comments
 (0)