A Slack bot for executing Carrier Risk Assessments using the MyCarrierPortal API within your Slack environment.
Brought to you by Anthony Fecarotta of linehaul.ai & linehaul.ai
- Docker and Docker Compose (Recommended)
- A Slack workspace with permissions to add apps
- MyCarrierPortal API access (including Bearer Token, Refresh Token, and Token Endpoint URL)
- Node.js >= 22.0.0 (if not using Docker)
This application uses a dual-container architecture with Docker Compose and connects to Slack via Bolt Socket Mode:
- mcpslackbot: Node.js application running the Slack Bolt Socket Mode client and MyCarrierPortal API integration
- libsql: Database server (Turso libSQL) for persistent token storage
Token persistence ensures OAuth refresh tokens survive container restarts and enables automatic token rotation without manual intervention.
git clone https://github.com/linehaul.ai/mcp-slackbot.git
cd mcp-slackbotCopy the example file:
cp .env.example .envEdit .env and fill in your credentials:
# MyCarrierPortal API Configuration
BEARER_TOKEN=your_bearer_token_here
REFRESH_TOKEN=your_refresh_token_here
TOKEN_ENDPOINT_URL=https://api.mycarrierpackets.com/token
CLIENT_ID=your_mcp_username
CLIENT_SECRET=your_mcp_password
# Slack Configuration
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
SLACK_SIGNING_SECRET=your_slack_signing_secret
SLACK_APP_TOKEN=xapp-your-slack-app-token
# Application Configuration
NODE_ENV=production
# Database Configuration (optional - defaults shown)
LIBSQL_URL=http://libsql:8081Important Notes:
CLIENT_IDandCLIENT_SECRETare your MyCarrierPortal username and password (used for initial token generation only)- These credentials are NOT sent with refresh token requests
- Initial
BEARER_TOKENandREFRESH_TOKENvalues are only used on first startup to seed the database
Production mode:
docker compose up -dThis command will:
- Pull the libSQL server image
- Build the Node.js application image
- Create a persistent volume for token storage
- Start both containers in the background
Development mode (with logs visible):
docker compose upCheck that both containers are running:
docker compose psExpected output:
NAME IMAGE STATUS
libsql ghcr.io/tursodatabase/libsql-server:latest Up
mcpslackbot mcpslackbot Up
View application logs:
# All logs
docker compose logs -f
# Just the app
docker compose logs -f mcpslackbot
# Just the database
docker compose logs -f libsqlLook for these startup messages:
Database initializedLoaded tokens from databaseORNo tokens in database, saving from environmentSlack Bolt app is running in Socket Mode
docker compose exec mcpslackbot node tests/test_refresh.jsExpected output:
Starting token refresh test...
Loaded tokens from database
Current Bearer Token (first 20 chars): 2_HG7Zvg3wqYkqtXxKge...
Current Refresh Token: a2afa1653b4b4b048398...
Attempting to refresh access token...
Response received: { ... }
Access token refreshed successfully.
New refresh token received.
Tokens saved to database
Test successful!
- First Startup: Tokens from environment variables are saved to the libSQL database
- Subsequent Startups: Tokens are loaded from the database (not environment variables)
- Token Refresh: When access token expires (14 days), the app automatically:
- Detects 401 error from MyCarrierPortal API
- Calls refresh endpoint with current refresh token
- Receives new access token and refresh token
- Saves both to database
- Retries the failed API call
- Container Restart: Tokens persist in the libSQL volume and are automatically loaded
View current tokens:
docker compose exec mcpslackbot node -e "
const { createClient } = require('@libsql/client');
const db = createClient({ url: 'http://libsql:8081' });
db.execute('SELECT bearer_token, refresh_token, updated_at FROM tokens').then(r => console.log(r.rows));
"Manually update tokens in database:
docker compose exec mcpslackbot node -e "
const { saveTokens } = require('./db');
saveTokens('new_bearer_token', 'new_refresh_token').then(() => console.log('Done'));
"Backup database:
# Stop containers first
docker compose down
# Copy the volume data
docker run --rm -v mcp-slackbot_libsql-data:/data -v $(pwd):/backup alpine \
tar czf /backup/libsql-backup-$(date +%Y%m%d).tar.gz -C /data .
# Restart containers
docker compose up -dRestore database:
docker compose down
docker run --rm -v mcp-slackbot_libsql-data:/data -v $(pwd):/backup alpine \
tar xzf /backup/libsql-backup-YYYYMMDD.tar.gz -C /data
docker compose up -dThis repository includes a self-hosted GitHub Actions workflow for automatic deployment.
- Install GitHub Actions runner on your server (Proxmox VM, etc.)
- Configure the runner for your repository
- Add required secrets to your GitHub repository
Navigate to Settings > Secrets and variables > Actions and add:
| Secret Name | Description | Example |
|---|---|---|
BEARER_TOKEN |
Initial MyCarrierPortal access token | VyTeZfFdtMagZ03J... |
REFRESH_TOKEN |
Initial MyCarrierPortal refresh token | a2afa1653b4b4b04... |
TOKEN_ENDPOINT_URL |
MyCarrierPortal token endpoint | https://api.mycarrierpackets.com/token |
CLIENT_ID |
MyCarrierPortal username | your_username |
CLIENT_SECRET |
MyCarrierPortal password | your_password |
SLACK_SIGNING_SECRET |
Slack app signing secret | 1234567890abcdef... |
SLACK_BOT_TOKEN |
Slack bot token | xoxb-... |
SLACK_APP_TOKEN |
Slack app-level token (Socket Mode) | xapp-... |
Note: After the first deployment, tokens will be managed automatically via the database. GitHub Secrets only provide initial values.
The workflow triggers on:
- Push to
socket-modebranch - Manual trigger via GitHub Actions UI
The deploy job only runs when all of the following are true:
- The ref is
refs/heads/socket-mode - The repository is
freightCognition/mcp-slackbot - The event is not from a fork
- Any required
productionenvironment approvals have been granted
Deployment steps:
- Checkout the repository
- Create
.envfile from GitHub Secrets - Build and start containers with
docker compose -f docker-compose.yml -f docker-compose.runner.yml up -d --build - Remove
.envfrom the workspace
View deployment logs:
- Go to Actions tab in GitHub
- Click on the latest workflow run
- Expand each step to see detailed logs
Manual deployment:
- Go to Actions tab
- Select Deploy (self-hosted) workflow
- Click Run workflow
- Select the
socket-modebranch in the branch dropdown - Approve the
productionenvironment deployment if prompted
If you're starting fresh or need to reset tokens:
-
Obtain fresh tokens from MyCarrierPortal using password grant:
curl -X POST https://api.mycarrierpackets.com/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=password&username=YOUR_USERNAME&password=YOUR_PASSWORD"
-
Update
.envfile with the response tokens:BEARER_TOKEN=<access_token from response> REFRESH_TOKEN=<refresh_token from response>
-
Restart containers:
docker compose down docker compose up -d
Tokens are automatically rotated! You don't need to manually update them.
If you ever need to force a refresh:
docker compose exec mcpslackbot node tests/test_refresh.jsIf the database becomes corrupted:
- Get fresh tokens (see Scenario 1)
- Stop containers:
docker compose down
- Remove the volume:
docker volume rm mcp-slackbot_libsql-data
- Update
.envwith fresh tokens - Start containers:
docker compose up -d
The database will be recreated and seeded with the new tokens.
If you prefer not to use Docker, you can run the application directly with Node.js. Note: You'll need to run your own libSQL server or modify the code to use a different database.
- Node.js >= 22.0.0
- libSQL server running (or modify code for different database)
-
Install dependencies:
pnpm install --frozen-lockfile
-
Configure environment variables: Create a
.envfile with all required variables (see section 2 above) -
Run libSQL server separately:
# Download and run sqld # See: https://github.com/tursodatabase/libsql
-
Run the application:
# Development mode (auto-restart on changes) pnpm dev # Production mode pnpm start # Or with PM2 pnpm pm2:start pnpm pm2:logs pnpm pm2:stop
To use this bot, you need to create a Slack App:
- Go to https://api.slack.com/apps and click "Create New App"
- Choose "From scratch"
- Name your app (e.g., "MCP Bot") and select your workspace
- Navigate to Features > Slash Commands
- Click Create New Command
- Configure:
- Command:
/risk - Request URL: Use a placeholder URL (Socket Mode does not require a public endpoint)
- Short Description: "Fetch MCP Carrier Risk Assessment"
- Usage Hint:
[MC number]
- Command:
- Save
Socket Mode eliminates the need for a public HTTP endpoint or ngrok.
- Navigate to Settings > Socket Mode
- Enable Socket Mode
- Generate an App-Level Token with the
connections:writescope - Copy the token to use as
SLACK_APP_TOKEN
- Navigate to Features > OAuth & Permissions
- Add Bot Token Scopes:
commands- Required for slash commandschat:write- Required to send messages
- Click Install to Workspace
- Copy the Bot User OAuth Token (starts with
xoxb-) to use asSLACK_BOT_TOKEN
- Navigate to Settings > Basic Information
- Find Signing Secret under "App Credentials"
- Copy to use as
SLACK_SIGNING_SECRET
| Variable | Required | Description | Example |
|---|---|---|---|
BEARER_TOKEN |
Yes | MyCarrierPortal access token | VyTeZfFdtMagZ03J... |
REFRESH_TOKEN |
Yes | MyCarrierPortal refresh token | a2afa1653b4b4b04... |
TOKEN_ENDPOINT_URL |
Yes | Token refresh endpoint | https://api.mycarrierpackets.com/token |
CLIENT_ID |
Yes | MyCarrierPortal username | your_username |
CLIENT_SECRET |
Yes | MyCarrierPortal password | your_password |
SLACK_SIGNING_SECRET |
Yes | Slack app signing secret | 1234567890abcdef... |
SLACK_BOT_TOKEN |
Yes | Slack bot token | xoxb-... |
SLACK_APP_TOKEN |
Yes | Slack app-level token (Socket Mode) | xapp-... |
NODE_ENV |
No | Environment mode | production or development |
LIBSQL_URL |
No | Database connection URL | http://libsql:8081 (default) |
The project includes comprehensive test scripts for verifying functionality.
# Run all tests (vitest)
pnpm test
# Test bearer token against API
pnpm test:token
# Test refresh token functionality
pnpm test:refreshQuick test:
docker compose exec mcpslackbot node tests/test_refresh.jsExpected success output:
Starting token refresh test...
Loaded tokens from database
Current Bearer Token (first 20 chars): 2_HG7Zvg3wqYkqtXxKge...
Current Refresh Token: a2afa1653b4b4b048398...
Attempting to refresh access token...
Response received: {
"access_token": "tTG1sIov5mITHION...",
"token_type": "bearer",
"expires_in": 1209599,
"refresh_token": "bc306b1405554e03...",
"userName": "...",
".issued": "Wed, 31 Dec 2025 04:32:53 GMT",
".expires": "Wed, 14 Jan 2026 04:32:53 GMT"
}
Access token refreshed successfully.
New refresh token received.
Tokens saved to database
Test successful!
New Bearer Token (first 20 chars): tTG1sIov5mITHIONeI1_...
New Refresh Token: bc306b1405554e038b82...
Verify tokens persist across restarts:
# Restart the app container (database stays running)
docker compose restart mcpslackbot
# Wait for startup
sleep 5
# Check logs - should show "Loaded tokens from database"
docker compose logs mcpslackbot | grep -i token
# Verify tokens still work
docker compose exec mcpslackbot node tests/test_refresh.jsTest automatic token refresh when access token expires:
- Use an old/expired
BEARER_TOKEN - Trigger a Slack command:
/risk MC123456 - Watch logs for automatic refresh:
docker compose logs -f mcpslackbot
Expected log sequence:
Fetching data for MC number: mc123456, attempt 1
Access token expired or invalid. Attempting refresh...
Attempting to refresh access token...
Access token refreshed successfully.
New refresh token received.
Tokens saved to database
Token refreshed. Retrying API call...
Fetching data for MC number: mc123456, attempt 2
Data received for MC number: mc123456
Sending Slack response for MC number: mc123456
# Watch for refresh activity
docker compose logs -f mcpslackbot | grep -i -E "(refresh|token|401)"
# Check database last update time
docker compose exec mcpslackbot node -e "
const { createClient } = require('@libsql/client');
const db = createClient({ url: 'http://libsql:8081' });
db.execute('SELECT updated_at FROM tokens WHERE id = 1').then(r => console.log('Last updated:', r.rows[0]?.updated_at));
"✅ Everything working correctly:
- Database initializes on startup
- Tokens loaded from database (not environment)
- Refresh test passes
- Slack commands work
- 401 errors trigger automatic refresh
- New tokens saved to database
- Container restarts preserve tokens
❌ Potential issues:
REFRESH_TOKEN not found in database or environment- Need to seed databaseError refreshing access token: {"error": "invalid_grant"}- Refresh token expired/invalidError loading tokens from database- Database connection issue- Tokens revert after restart - Volume not persisting correctly
address already in use- Port 8081 is in use, checkdocker psand stop conflicting container
Check logs:
docker compose logsCommon issues:
- Port 8081 already in use - change port mapping in
docker-compose.ymlfor libsql service - Missing environment variables - verify
.envfile exists and is complete - Permission issues - ensure user can access Docker socket
Verify libSQL is running:
docker compose ps libsql
curl http://localhost:8081/healthCheck network:
docker compose exec mcpslackbot curl -f http://libsql:8081/healthReset database:
docker compose down
docker volume rm mcp-slackbot_libsql-data
docker compose up -dCheck volume exists:
docker volume ls | grep libsqlInspect volume:
docker volume inspect mcp-slackbot_libsql-dataVerify database has data:
docker compose exec mcpslackbot node -e "
const { createClient } = require('@libsql/client');
const db = createClient({ url: 'http://libsql:8081' });
db.execute('SELECT COUNT(*) as count FROM tokens').then(r => console.log('Token count:', r.rows[0]?.count));
"Get detailed error:
docker compose exec mcpslackbot node tests/test_refresh.jsCheck token endpoint URL:
docker compose exec mcpslackbot printenv TOKEN_ENDPOINT_URL
# Should be: https://api.mycarrierpackets.com/tokenCheck libSQL port:
docker compose exec mcpslackbot printenv LIBSQL_URL
# Should be: http://libsql:8081Obtain fresh tokens:
curl -X POST https://api.mycarrierpackets.com/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=YOUR_USERNAME&password=YOUR_PASSWORD"Then update tokens in database (see "Updating Tokens" section).
Verify Slack configuration:
- Socket Mode is enabled for the app
SLACK_APP_TOKENuses an app-level token withconnections:write- Signing secret matches
- Bot token has required scopes (
commands,chat:write)
- ✅ Never commit
.envfiles - Already in.gitignore - ✅ Use Docker secrets in production - Configured in
docker-compose.yml - ✅ Rotate credentials regularly - Automatic for access/refresh tokens
- ✅ Backup database regularly - Contains sensitive tokens
- ✅ Use
.env.example- Never contains real credentials - ✅ Implement volume encryption - Consider for libsql-data volume
- ✅ Monitor access logs - Track unusual activity
# Pull latest code
git pull origin main
# Rebuild and restart
docker compose down
docker compose up -d --build
# Verify
docker compose logs -fView database statistics:
docker compose exec mcpslackbot node -e "
const { createClient } = require('@libsql/client');
const db = createClient({ url: 'http://libsql:8081' });
db.execute('SELECT * FROM tokens').then(r => console.log(JSON.stringify(r.rows, null, 2)));
"Scheduled backups:
Create a cron job:
# Edit crontab
crontab -e
# Add daily backup at 2 AM
0 2 * * * cd /path/to/mcp-slackbot && docker compose down && docker run --rm -v mcp-slackbot_libsql-data:/data -v $(pwd)/backups:/backup alpine tar czf /backup/libsql-$(date +\%Y\%m\%d).tar.gz -C /data . && docker compose up -dThis project is licensed under version 3 of the GNU Affero General Public License (AGPL-3.0). See the LICENSE.TXT file for details.
For issues or questions:
- GitHub Issues: https://github.com/linehaul.ai/mcp-slackbot/issues
- Contact: Anthony Fecarotta (linehaul.ai / linehaul.ai)
