Skip to main content
Glama
cnye36

Slack MCP Server

by cnye36
README.md
# 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/install` and `/slack/oauth/callback` endpoints
- **MCP Endpoint**: `/mcp` for all MCP protocol communication
- **Session Management**: Automatic session pruning with configurable TTL
- **Installation Storage**: Pluggable storage interface (in-memory by default)

## Prerequisites

1. **Slack App** - Create a Slack app at [api.slack.com/apps](https://api.slack.com/apps)
2. **Node.js** - Version 18+ required
3. **pnpm** - Package manager (or npm/yarn)

## Quick Start

### 1. Install Dependencies

```bash
pnpm install
```

### 2. Configure Environment

Copy the example environment file and fill in your values:

```bash
cp .env.example .env
```

Required variables:
- `SLACK_CLIENT_ID` - From your Slack app's OAuth settings
- `SLACK_CLIENT_SECRET` - From your Slack app's OAuth settings
- `SLACK_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

```bash
pnpm dev
```

The server will start on `http://localhost:8080` by default.

### 4. Install Slack App

1. Navigate to `http://localhost:8080/slack/install`
2. Authorize the app for your workspace
3. 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_ORIGINS` if 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 ID
- `channel_id` (string) - Channel/DM ID
- `limit` (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 ID
- `types` (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 messages
- `channels:history` - Read public channel messages
- `channels:read` - List public channels

Additional recommended scopes:
- `groups:history` - Read private channel messages
- `groups:read` - List private channels
- `im:history` - Read direct messages
- `im:read` - List direct messages
- `mpim:history` - Read group DM messages
- `mpim:read` - List group DMs

## Production Deployment

### Using Docker

Build the image:

```bash
docker build -t slack-mcp-server .
```

Run the container:

```bash
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_KEY` to a strong random value
- [ ] Configure `ALLOWED_ORIGINS` with your frontend domains
- [ ] Use HTTPS for all endpoints (required by Slack)
- [ ] Set `PUBLIC_BASE_URL` to your public HTTPS URL
- [ ] Replace `InMemoryInstallStore` with persistent storage (Redis/PostgreSQL)
- [ ] Set up monitoring and alerting
- [ ] Configure log aggregation
- [ ] Set appropriate `SESSION_TTL_MS` for 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:

```typescript
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

```bash
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 `Authorization` header
- 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`:

```typescript
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

```bash
# 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:
1. Check the [IMPLEMENTATION.md](./IMPLEMENTATION.md) guide
2. Review Slack API documentation at [api.slack.com](https://api.slack.com)
3. Check MCP documentation at [modelcontextprotocol.io](https://modelcontextprotocol.io)

## Contributing

Contributions welcome! Please:
1. Follow the existing code style
2. Add tests for new features
3. Update documentation
4. Ensure all lints pass