Building AI agents that interact with Slack requires secure OAuth authentication, proper token management, and reliable tool execution. This guide shows you how to connect GPT-5 to Slack using Arcade's Model Context Protocol (MCP) implementation, enabling your agents to send messages, read conversations, and manage channels with production-grade security.
Prerequisites
Before starting, ensure you have:
- Arcade.dev account with API key
- Python 3.10+ or Node.js 18+ installed
- OpenAI API key with GPT-4 or GPT-5 access
- Slack workspace admin access for OAuth configuration
- Basic understanding of async/await patterns
Architecture Overview
The integration uses a three-layer architecture:
GPT Model → Arcade MCP Server → Slack API
Arcade sits between your AI model and Slack, handling OAuth flows, token management, and secure API calls. This architecture ensures credentials never reach the language model while maintaining user-specific authentication contexts.
The Arcade Engine serves as both an MCP server and authentication broker, supporting the HTTP streamable transport protocol that OpenAI's agents require. When your GPT agent needs to call Slack tools, Arcade manages the entire authorization lifecycle.
Setting Up the Development Environment
Install Required Packages
For Python implementations:
pip install arcadepy openai agents-arcade
For JavaScript/TypeScript:
npm install @arcadeai/arcadejs openai @openai/agents
Configure Environment Variables
Create a .env file in your project root:
ARCADE_API_KEY=your_arcade_api_key
OPENAI_API_KEY=your_openai_api_key
Get your Arcade API key from the dashboard at api.arcade.dev/dashboard.
Configuring Slack Authentication
Create Slack App Credentials
- Navigate to api.slack.com/apps and create a new app
- Under "OAuth & Permissions," add these redirect URLs:
https://api.arcade.dev/v1/auth/callback(for Arcade Cloud)- Your custom domain if self-hosting
- Configure the required OAuth scopes:
chat:write
channels:read
channels:history
users:read
im:write
The Slack toolkit requires these scopes for sending messages and reading conversations. Add additional scopes based on your use case.
Register Slack Provider in Arcade
Add your Slack credentials to Arcade via the dashboard or configuration file:
Via Dashboard:
- Navigate to OAuth → Providers → Add OAuth Provider
- Select Slack from the Included Providers tab
- Enter your Client ID and Client Secret
- Save the configuration
Via Configuration (Self-Hosted):
Edit your engine.yaml file:
auth:
providers:
- id: slack-oauth
description: "Slack workspace authentication"
enabled: true
type: oauth2
provider_id: slack
client_id: ${env:SLACK_CLIENT_ID}
client_secret: ${env:SLACK_CLIENT_SECRET}
Learn more about Slack authentication configuration.
Building the GPT-Slack Agent
Python Implementation with OpenAI Agents
Create a file slack_agent.py:
import os
import asyncio
from arcadepy import AsyncArcade
from agents import Agent, Runner
from agents_arcade import get_arcade_tools
from agents_arcade.errors import AuthorizationError
async def main():
# Initialize Arcade client
client = AsyncArcade(api_key=os.getenv("ARCADE_API_KEY"))
# Get Slack toolkit from Arcade MCP server
slack_tools = await get_arcade_tools(
client,
toolkits=["slack"],
limit=30
)
# Create GPT agent with Slack capabilities
slack_agent = Agent(
name="Slack Assistant",
instructions="""You are a helpful assistant that manages Slack communications.
You can send messages, read conversations, and help users stay organized.
Always confirm actions before executing them.""",
model="gpt-5",
tools=slack_tools
)
# Unique user identifier for auth
user_id = "user@company.com"
try:
result = await Runner.run(
starting_agent=slack_agent,
input="Send a message to #general saying 'Hello from GPT!'",
context={"user_id": user_id}
)
print("Agent response:", result.final_output)
except AuthorizationError as e:
url = getattr(e, "url", str(e))
print(f"Authorization required. Please visit: {url}")
print("User must complete OAuth flow before continuing")
if __name__ == "__main__":
asyncio.run(main())
The agents-arcade package provides seamless integration with OpenAI's agent framework, handling tool conversion and authorization automatically.
JavaScript Implementation
Create slackAgent.js:
import Arcade from '@arcadeai/arcadejs';
import { Agent, run } from '@openai/agents';
import { toZod, isAuthorizationRequiredError } from '@arcadeai/arcadejs/lib';
async function createSlackAgent() {
const client = new Arcade({
apiKey: process.env.ARCADE_API_KEY
});
// Fetch Slack MCP server tools
const slackToolkit = await client.tools.list({
toolkit: 'slack',
limit: 30
});
// Convert to OpenAI agent tools
const tools = toZod({
tools: slackToolkit.items,
client: client,
userId: 'user@company.com'
});
// Create GPT agent
const agent = new Agent({
name: 'Slack Assistant',
instructions: 'Help users communicate effectively on Slack',
model: 'gpt-5',
tools: tools
});
return { agent, client };
}
async function executeSlackAction() {
const { agent, client } = await createSlackAgent();
try {
const result = await run({
agent: agent,
input: 'List my recent Slack messages'
});
console.log('Response:', result.text);
} catch (error) {
if (isAuthorizationRequiredError(error)) {
console.log('Please authorize:', error.url);
}
}
}
executeSlackAction();
Reference the JavaScript client documentation for additional configuration options.
Handling User Authorization
When users first interact with Slack tools, they must authorize access. Arcade manages this OAuth flow automatically.
Authorization Flow Pattern
from arcadepy import AsyncArcade
from arcadepy.auth import wait_for_authorization_completion
async def handle_authorization(client, user_id, tool_name):
# Check if authorization needed
auth_response = await client.tools.authorize(
tool_name=tool_name,
user_id=user_id
)
if auth_response.status != "completed":
# User needs to visit authorization URL
print(f"Authorize at: {auth_response.url}")
# Wait for user to complete OAuth flow
await wait_for_authorization_completion(
client,
auth_response.id
)
print("Authorization complete!")
# Tool is now ready to use
return True
The authorization URL directs users to Slack's OAuth consent screen. After approval, Arcade securely stores the access token and handles refresh automatically. Learn more about authorization patterns.
Working with Slack Tools
Available Operations
The Slack MCP server provides these pre-built tools:
Messaging:
Slack.SendMessage- Send messages to channels, DMs, or groupsSlack.GetMessages- Retrieve conversation historySlack.GetConversationMetadata- Fetch channel details
User Management:
Slack.GetUsers- List workspace membersSlack.GetUserInfo- Retrieve user profiles
Conversation Management:
Slack.ListConversations- Get all accessible channels
Sending Messages Programmatically
# Direct tool execution
response = await client.tools.execute(
tool_name="Slack.SendMessage",
input={
"channel_name": "general",
"message": "Deployment complete! All systems operational."
},
user_id=user_id
)
print(response.output.value)
Retrieving Conversation History
# Fetch recent messages with filtering
messages = await client.tools.execute(
tool_name="Slack.GetMessages",
input={
"channel_name": "engineering",
"limit": 50,
"oldest_relative": "01:00:00" # Last hour
},
user_id=user_id
)
for msg in messages.output.value:
print(f"{msg['user']}: {msg['text']}")
The toolkit supports time-based filtering using relative offsets or absolute timestamps. See the Slack reference documentation for all available parameters.
Building Multi-User Slack Agents
Production applications require managing multiple users' Slack access. Arcade handles per-user token storage automatically.
Multi-User Agent Pattern
class SlackAgentManager:
def __init__(self):
self.client = AsyncArcade()
self.user_caches = {}
async def get_user_tools(self, user_id: str):
# Cache tools per user for performance
if user_id not in self.user_caches:
tools = await get_arcade_tools(
self.client,
toolkits=["slack"],
user_id=user_id
)
self.user_caches[user_id] = tools
return self.user_caches[user_id]
async def create_agent_for_user(self, user_id: str):
tools = await self.get_user_tools(user_id)
return Agent(
name=f"Slack Agent ({user_id})",
instructions="Manage Slack communications securely",
model="gpt-5",
tools=tools
)
async def process_request(self, user_id: str, prompt: str):
agent = await self.create_agent_for_user(user_id)
result = await Runner.run(
starting_agent=agent,
input=prompt,
context={"user_id": user_id}
)
return result.final_output
This pattern ensures token isolation between users and improves performance through caching. The multi-user authentication guide provides additional patterns applicable to Slack.
Deploying to Production
Cloud Deployment with Arcade Deploy
Arcade Deploy enables serverless hosting of your tools:
- Create
worker.tomlconfiguration:
[[worker]]
[worker.config]
id = "slack-agent-worker"
secret = "${env:WORKER_SECRET}"
enabled = true
timeout = 30
[worker.pypi_source]
packages = ["arcadepy", "agents-arcade"]
- Deploy using the CLI:
arcade deploy
- Configure your agent to use the deployed worker endpoint
Self-Hosted MCP Server
For enterprise deployments requiring on-premises hosting:
# engine.yaml
api:
host: 0.0.0.0
port: 9099
auth:
providers:
- id: slack-prod
enabled: true
type: oauth2
provider_id: slack
client_id: ${env:SLACK_CLIENT_ID}
client_secret: ${env:SLACK_CLIENT_SECRET}
workers:
- id: slack-worker
enabled: true
http:
uri: "http://localhost:8002"
secret: ${env:WORKER_SECRET}
Start the local engine:
arcade-engine -c engine.yaml
Security Best Practices
Token Management
Arcade handles token encryption, rotation, and storage automatically. Credentials never reach your application code or the language model.
Key security features:
- OAuth 2.0 with PKCE for authorization
- Automatic access token refresh
- Encrypted token storage
- Per-user permission scoping
- Audit logging for all tool executions
Rate Limiting
Implement request throttling to prevent API abuse:
from asyncio import Semaphore
class RateLimitedSlackAgent:
def __init__(self, max_concurrent=5):
self.semaphore = Semaphore(max_concurrent)
self.client = AsyncArcade()
async def execute_with_limit(self, tool_name, input_data, user_id):
async with self.semaphore:
return await self.client.tools.execute(
tool_name=tool_name,
input=input_data,
user_id=user_id
)
Error Handling
Implement comprehensive error handling for production reliability:
from agents_arcade.errors import (
AuthorizationError,
ToolExecutionError
)
async def safe_tool_execution(client, tool_name, input_data, user_id):
try:
result = await client.tools.execute(
tool_name=tool_name,
input=input_data,
user_id=user_id
)
return result.output.value
except AuthorizationError as e:
# User needs to reauthorize
return {
"error": "authorization_required",
"url": str(e)
}
except ToolExecutionError as e:
# Tool-specific error (rate limit, invalid input)
return {
"error": "execution_failed",
"message": str(e)
}
Testing the Integration
Local Testing with MCP Inspector
Use the MCP Inspector to validate tool definitions:
# Point inspector to your local MCP server
arcade serve --mcp
# In another terminal
mcp-inspector http://localhost:8002/mcp
Integration Testing
import pytest
from arcadepy import AsyncArcade
@pytest.mark.asyncio
async def test_slack_message_send():
client = AsyncArcade()
response = await client.tools.execute(
tool_name="Slack.SendMessage",
input={
"channel_name": "test-channel",
"message": "Test message from integration suite"
},
user_id="test_user"
)
assert response.output.value["ok"] == True
Advanced Patterns
Custom Slack Tools
Build application-specific Slack tools using the Arcade TDK:
from arcade_tdk import tool, ToolContext
from arcade_tdk.auth import Slack
from slack_sdk import WebClient
@tool(
requires_auth=Slack(
scopes=["chat:write", "channels:read"]
)
)
async def send_formatted_report(
context: ToolContext,
channel: str,
data: dict
):
"""Send a formatted report to Slack channel"""
client = WebClient(token=context.authorization.token)
blocks = [
{
"type": "header",
"text": {"type": "plain_text", "text": data["title"]}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": f"*Status:* {data['status']}"},
{"type": "mrkdwn", "text": f"*Timestamp:* {data['timestamp']}"}
]
}
]
response = client.chat_postMessage(
channel=channel,
blocks=blocks,
text=data["title"]
)
return {"message_id": response["ts"]}
Integrating Multiple Services
Combine Slack with other toolkits for powerful workflows:
# Get tools from multiple MCP servers
tools = await get_arcade_tools(
client,
toolkits=["slack", "gmail", "github"]
)
agent = Agent(
name="Multi-Service Agent",
instructions="""
You can:
- Send Slack notifications
- Read and send Gmail
- Manage GitHub issues
Coordinate actions across all services.
""",
model="gpt-5",
tools=tools
)
View all available Arcade toolkits.
Monitoring and Observability
Tool Execution Metrics
Track agent performance and tool usage:
import time
from functools import wraps
def track_tool_execution(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start = time.time()
try:
result = await func(*args, **kwargs)
duration = time.time() - start
print(f"Tool executed in {duration:.2f}s")
return result
except Exception as e:
duration = time.time() - start
print(f"Tool failed after {duration:.2f}s: {e}")
raise
return wrapper
Logging Best Practices
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
async def execute_with_logging(client, tool_name, user_id):
logger.info(
f"Executing {tool_name}",
extra={"user_id": user_id}
)
result = await client.tools.execute(
tool_name=tool_name,
input={},
user_id=user_id
)
logger.info(
f"Tool execution complete",
extra={
"tool": tool_name,
"user": user_id,
"success": True
}
)
return result
Performance Optimization
Tool Caching
Cache tool definitions to reduce initialization overhead:
from functools import lru_cache
@lru_cache(maxsize=100)
async def get_cached_tools(toolkit_name: str):
client = AsyncArcade()
return await get_arcade_tools(
client,
toolkits=[toolkit_name]
)
Batch Operations
Process multiple Slack operations efficiently:
import asyncio
async def send_bulk_messages(client, messages, user_id):
tasks = [
client.tools.execute(
tool_name="Slack.SendMessage",
input=msg,
user_id=user_id
)
for msg in messages
]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
Conclusion
This implementation provides production-ready integration between GPT models and Slack using Arcade's MCP infrastructure. The architecture ensures secure authentication, reliable tool execution, and scalable multi-user support.
Key takeaways:
- Arcade handles OAuth complexity automatically
- MCP provides standardized tool calling
- Per-user authentication maintains security boundaries
- Pre-built toolkits accelerate development
For additional resources:
- Arcade Documentation
- Slack Toolkit Reference
- MCP Server Guide
- OpenAI Agents Integration
- GitHub Examples
Start building agents that take action with Arcade.dev.



