Slack MCP Server
A production-ready Model Context Protocol (MCP) server for Slack integration. This server enables AI assistants to interact with Slack workspaces through a secure, multi-tenant OAuth flow.
Features
- 🔐 Secure OAuth 2.0 Flow - Full Slack OAuth v2 implementation with state validation
- 🏢 Multi-Tenant Support - Handle multiple Slack workspaces simultaneously
- 🔄 Token Rotation - Automatic token refresh for long-lived installations
- 🛡️ Production Security - CORS, API key protection, Helmet.js, request timeouts
- 📊 Structured Logging - Pino logger with configurable log levels
- 🐳 Docker Ready - Multi-stage Dockerfile with non-root user
- ⚡ MCP Tools - Post messages, read channel history, list channels
Architecture
This server uses the Streamable HTTP transport for MCP, making it suitable for web-based AI assistants. It manages:
- OAuth Flow:
/slack/installand/slack/oauth/callbackendpoints - MCP Endpoint:
/mcpfor all MCP protocol communication - Session Management: Automatic session pruning with configurable TTL
- Installation Storage: Pluggable storage interface (in-memory by default)
Prerequisites
- Slack App - Create a Slack app at api.slack.com/apps
- Node.js - Version 18+ required
- pnpm - Package manager (or npm/yarn)
Quick Start
1. Install Dependencies
pnpm install
2. Configure Environment
Copy the example environment file and fill in your values:
cp .env.example .env
Required variables:
SLACK_CLIENT_ID- From your Slack app's OAuth settingsSLACK_CLIENT_SECRET- From your Slack app's OAuth settingsSLACK_REDIRECT_URI- Your callback URL (e.g.,https://yourdomain.com/slack/oauth/callback)SLACK_SCOPES- Comma-separated bot scopes (e.g.,chat:write,channels:history,channels:read)AFFINITYBOTS_MCP_API_KEY- Secret key to protect your MCP endpoint
3. Run Development Server
pnpm dev
The server will start on http://localhost:8080 by default.
4. Install Slack App
- Navigate to
http://localhost:8080/slack/install - Authorize the app for your workspace
- You'll be redirected back with a success message
5. Use MCP Tools
Connect your MCP client to http://localhost:8080/mcp with:
- Authorization header:
Bearer YOUR_AFFINITYBOTS_MCP_API_KEY - Origin header: Must match
ALLOWED_ORIGINSif configured
Available MCP Tools
slack_post_message
Post a message to a Slack channel.
Parameters:
team_id(string) - Slack workspace/team ID (T...)channel_id(string) - Channel/DM ID (C... or D...)text(string) - Message text (1-4000 characters)thread_ts(string, optional) - Thread timestamp to reply to
slack_get_channel_history
Read messages from a Slack channel.
Parameters:
team_id(string) - Slack workspace/team IDchannel_id(string) - Channel/DM IDlimit(number, optional) - Number of messages (1-200, default: 50)
slack_list_channels
List all channels in the workspace.
Parameters:
team_id(string) - Slack workspace/team IDtypes(string, optional) - Channel types:public_channel,private_channel,mpim,im(default:public_channel)limit(number, optional) - Number of channels (1-1000, default: 200)
Configuration
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
PORT | No | 8080 | Server port |
HOST | No | 0.0.0.0 | Server host |
PUBLIC_BASE_URL | No | http://localhost:8080 | Public URL for OAuth redirects |
SLACK_CLIENT_ID | Yes | - | Slack app client ID |
SLACK_CLIENT_SECRET | Yes | - | Slack app client secret |
SLACK_REDIRECT_URI | Yes | - | OAuth callback URL |
SLACK_SCOPES | Yes | - | Comma-separated bot scopes |
AFFINITYBOTS_MCP_API_KEY | Recommended | - | API key for MCP endpoint |
ALLOWED_ORIGINS | No | - | Comma-separated allowed origins for CORS |
SESSION_TTL_MS | No | 900000 | MCP session timeout (15 min) |
LOG_LEVEL | No | info | Log level: trace, debug, info, warn, error, fatal |
Required Slack Scopes
At minimum, your Slack app needs these bot token scopes:
chat:write- Post messageschannels:history- Read public channel messageschannels:read- List public channels
Additional recommended scopes:
groups:history- Read private channel messagesgroups:read- List private channelsim:history- Read direct messagesim:read- List direct messagesmpim:history- Read group DM messagesmpim:read- List group DMs
Production Deployment
Using Docker
Build the image:
docker build -t slack-mcp-server .
Run the container:
docker run -d \
-p 8080:8080 \
-e SLACK_CLIENT_ID=your_client_id \
-e SLACK_CLIENT_SECRET=your_client_secret \
-e SLACK_REDIRECT_URI=https://yourdomain.com/slack/oauth/callback \
-e SLACK_SCOPES=chat:write,channels:history,channels:read \
-e AFFINITYBOTS_MCP_API_KEY=your_secret_key \
-e ALLOWED_ORIGINS=https://yourdomain.com \
slack-mcp-server
Production Checklist
- Set
AFFINITYBOTS_MCP_API_KEYto a strong random value - Configure
ALLOWED_ORIGINSwith your frontend domains - Use HTTPS for all endpoints (required by Slack)
- Set
PUBLIC_BASE_URLto your public HTTPS URL - Replace
InMemoryInstallStorewith persistent storage (Redis/PostgreSQL) - Set up monitoring and alerting
- Configure log aggregation
- Set appropriate
SESSION_TTL_MSfor your use case - Enable Slack token rotation in your app settings (recommended)
- Set up rate limiting at the reverse proxy level
- Configure health check monitoring on
/health
Storage
The default InMemoryInstallStore is suitable for development but not for production. For production, implement the InstallStore interface with a persistent backend:
export interface InstallStore {
upsert(install: SlackInstallation): Promise<void>;
getByTeamId(teamId: string): Promise<SlackInstallation | null>;
}
Recommended storage options:
- PostgreSQL - Best for relational data and complex queries
- Redis - Fast, simple key-value storage
- MongoDB - Document-based storage
- DynamoDB - Serverless AWS option
Security Features
Implemented
- ✅ CSRF protection via OAuth state parameter
- ✅ API key authentication for MCP endpoint
- ✅ CORS with origin allowlist
- ✅ Helmet.js security headers
- ✅ Request timeouts (30s)
- ✅ Session expiration and pruning
- ✅ Automatic token refresh
- ✅ Secure error messages (no info leakage)
- ✅ Non-root Docker user
Recommended Additional Security
- Add rate limiting (e.g., express-rate-limit)
- Use a reverse proxy (nginx, Caddy) with TLS
- Implement request size limits
- Add request ID tracking
- Set up WAF rules
- Enable Slack signing secret verification for webhooks (if added)
Monitoring
Health Check
curl http://localhost:8080/health
Returns ok if the server is running.
Logs
The server uses structured JSON logging via Pino. Key log events:
- OAuth flow initiation and completion
- MCP session creation and expiration
- Token refresh operations
- API errors and warnings
- Security events (unauthorized access, invalid origins)
Set LOG_LEVEL=debug for detailed debugging.
Troubleshooting
OAuth Errors
"Invalid or expired state"
- The OAuth state token expired (10 min TTL)
- Try the installation flow again
"Slack OAuth failed: invalid_code"
- The authorization code was already used or expired
- Restart the installation flow
"Origin not allowed"
- Your frontend origin is not in
ALLOWED_ORIGINS - Add your origin to the environment variable
MCP Errors
"Unauthorized"
- Missing or invalid
Authorizationheader - Ensure you're sending
Bearer YOUR_API_KEY
"Unknown or expired session"
- MCP session expired (default 15 min)
- Reinitialize the MCP connection
"No Slack installation found"
- The workspace hasn't installed the app
- Complete the OAuth flow first
Token Errors
"Token refresh failed"
- The refresh token is invalid or revoked
- User needs to reinstall the app
- Check if token rotation is enabled in Slack app settings
Development
Project Structure
slack-mcp-server/
├── src/
│ ├── index.ts # Main server and routing
│ ├── mcp.ts # MCP tool definitions
│ ├── slackOauth.ts # OAuth flow handlers
│ ├── slackClient.ts # Slack API client with token refresh
│ ├── installStore.ts # Installation storage interface
│ └── security.ts # Security utilities
├── Dockerfile
├── package.json
├── tsconfig.json
└── README.md
Adding New Tools
Register new tools in src/mcp.ts:
server.tool(
"tool_name",
"Tool description",
{
team_id: z.string(),
// ... other parameters
},
async ({ team_id, ...params }) => {
const slack = await getSlackClientForTeam(store, team_id);
// ... implement tool logic
return { content: [{ type: "text", text: "result" }] };
}
);
Testing
# Run development server with debug logging
LOG_LEVEL=debug pnpm dev
# Build for production
pnpm build
# Run production build
pnpm start
License
MIT
Support
For issues and questions:
- Check the IMPLEMENTATION.md guide
- Review Slack API documentation at api.slack.com
- Check MCP documentation at modelcontextprotocol.io
Contributing
Contributions welcome! Please:
- Follow the existing code style
- Add tests for new features
- Update documentation
- Ensure all lints pass