MCP Server Background Jobs: Async Processing Guide 2026
Background job processing is essential for building production-ready MCP servers for ChatGPT apps. When users request heavy computations, data exports, or long-running tasks through ChatGPT, you need asynchronous processing to maintain responsiveness and prevent timeouts.
Synchronous processing blocks the conversation thread until tasks complete—unacceptable for operations lasting more than 5 seconds. Background jobs solve this by queuing tasks, processing them asynchronously, and notifying users when complete. This architectural pattern enables ChatGPT apps to handle report generation, bulk data processing, AI model training, email campaigns, and scheduled maintenance without degrading user experience.
In this guide, you'll implement production-grade background job processing using Bull (Redis-backed job queue), node-cron (scheduled tasks), and monitoring dashboards. Whether you're building high-performance ChatGPT applications or scaling MCP servers for thousands of concurrent users, mastering background jobs is non-negotiable.
Job Queue Setup with BullMQ
BullMQ is the industry-standard job queue for Node.js, offering Redis-backed persistence, job priorities, delays, retries, and concurrency control. Unlike in-memory queues that lose jobs on server restart, BullMQ ensures durability and scalability.
Installing Dependencies
First, install BullMQ and Redis client:
npm install bullmq ioredis
Start a Redis instance (required for queue backend):
# Using Docker (recommended for development)
docker run -d --name redis-queue -p 6379:6379 redis:7-alpine
# Or use Redis Cloud (production)
# Get connection string from https://redis.com/try-free/
Queue Configuration
Create a centralized queue manager at src/queues/queueManager.js:
import { Queue, Worker } from 'bullmq';
import Redis from 'ioredis';
// Redis connection (reuse across queues)
const connection = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD,
maxRetriesPerRequest: null, // Required for BullMQ
});
// Create job queues
export const reportQueue = new Queue('report-generation', {
connection,
defaultJobOptions: {
attempts: 3, // Retry failed jobs 3 times
backoff: {
type: 'exponential',
delay: 2000, // Start with 2s, then 4s, 8s
},
removeOnComplete: 100, // Keep last 100 completed jobs
removeOnFail: 500, // Keep last 500 failed jobs
},
});
export const emailQueue = new Queue('email-campaigns', {
connection,
defaultJobOptions: {
attempts: 5,
backoff: { type: 'fixed', delay: 5000 },
},
});
export const cleanupQueue = new Queue('database-cleanup', { connection });
// Helper: Add job with priority and delay
export async function addReportJob(userId, reportType, priority = 5) {
return await reportQueue.add(
'generate-report',
{ userId, reportType, timestamp: Date.now() },
{
priority, // 1 = highest, 10 = lowest
delay: 0, // Process immediately (or delay in ms)
}
);
}
Concurrency Control
Limit concurrent jobs to prevent resource exhaustion:
// In your worker configuration
const worker = new Worker('report-generation', processReport, {
connection,
concurrency: 5, // Process max 5 reports simultaneously
limiter: {
max: 100, // Max 100 jobs per duration
duration: 60000, // 1 minute
},
});
This prevents a single report spike from overwhelming your MCP server while ensuring fair resource allocation across all ChatGPT users.
Cron Jobs for Scheduled Tasks
Scheduled tasks handle recurring operations like database cleanup, report generation, cache invalidation, and data synchronization. Use node-cron for simple, reliable scheduling.
Installing node-cron
npm install node-cron
Cron Job Patterns
Create a scheduler at src/jobs/scheduler.js:
import cron from 'node-cron';
import { cleanupQueue, reportQueue } from './queueManager.js';
import { db } from '../database.js';
// Run every day at 2 AM (server time)
cron.schedule('0 2 * * *', async () => {
console.log('[CRON] Starting daily database cleanup');
await cleanupQueue.add('cleanup-old-jobs', {
olderThan: Date.now() - 30 * 24 * 60 * 60 * 1000, // 30 days
});
await cleanupQueue.add('cleanup-expired-sessions', {
olderThan: Date.now() - 7 * 24 * 60 * 60 * 1000, // 7 days
});
});
// Generate daily summary reports every day at 8 AM
cron.schedule('0 8 * * *', async () => {
console.log('[CRON] Generating daily summary reports');
const users = await db.collection('users')
.where('subscription', '==', 'professional')
.get();
for (const user of users.docs) {
await reportQueue.add('daily-summary', {
userId: user.id,
reportType: 'daily-usage',
date: new Date().toISOString().split('T')[0],
}, {
priority: 3, // Higher priority than ad-hoc reports
});
}
});
// Clear Redis cache every 6 hours
cron.schedule('0 */6 * * *', async () => {
console.log('[CRON] Clearing expired cache entries');
await cleanupQueue.add('clear-cache', {
pattern: 'cache:*',
olderThan: Date.now() - 6 * 60 * 60 * 1000,
});
});
// Health check every 5 minutes
cron.schedule('*/5 * * * *', async () => {
const queueHealth = await reportQueue.getJobCounts();
if (queueHealth.failed > 100) {
console.error('[ALERT] Too many failed jobs:', queueHealth);
// Send alert to monitoring system
}
});
console.log('[SCHEDULER] All cron jobs initialized');
Cron Schedule Syntax
┌─────────── minute (0 - 59)
│ ┌───────── hour (0 - 23)
│ │ ┌─────── day of month (1 - 31)
│ │ │ ┌───── month (1 - 12)
│ │ │ │ ┌─── day of week (0 - 6, Sunday = 0)
│ │ │ │ │
* * * * *
Examples:
'0 2 * * *' → Every day at 2 AM
'*/15 * * * *' → Every 15 minutes
'0 8 * * 1' → Every Monday at 8 AM
'0 0 1 * *' → First day of every month at midnight
Use crontab.guru to validate complex schedules before deployment.
Job Processing Implementation
Workers consume jobs from queues, execute business logic, track progress, and handle failures. Proper worker implementation ensures reliability and observability.
Report Generation Worker
Create src/workers/reportWorker.js:
import { Worker } from 'bullmq';
import { connection } from '../queues/queueManager.js';
import { generatePDF } from '../services/pdfGenerator.js';
import { uploadToStorage } from '../services/storage.js';
import { notifyUser } from '../services/notifications.js';
async function processReport(job) {
const { userId, reportType, timestamp } = job.data;
try {
// Update progress (0-100)
await job.updateProgress(10);
console.log(`[WORKER] Fetching data for ${reportType} report (user: ${userId})`);
const reportData = await fetchReportData(userId, reportType);
await job.updateProgress(40);
// Generate PDF (CPU-intensive operation)
console.log(`[WORKER] Generating PDF for job ${job.id}`);
const pdfBuffer = await generatePDF(reportData, reportType);
await job.updateProgress(70);
// Upload to cloud storage
const fileUrl = await uploadToStorage(
`reports/${userId}/${timestamp}-${reportType}.pdf`,
pdfBuffer
);
await job.updateProgress(90);
// Notify user via ChatGPT (update widget state)
await notifyUser(userId, {
type: 'report-ready',
reportType,
downloadUrl: fileUrl,
generatedAt: new Date().toISOString(),
});
await job.updateProgress(100);
return { success: true, fileUrl, fileSize: pdfBuffer.length };
} catch (error) {
console.error(`[WORKER] Job ${job.id} failed:`, error.message);
throw error; // BullMQ will retry based on job options
}
}
// Start worker with error handling
const reportWorker = new Worker('report-generation', processReport, {
connection,
concurrency: 3,
});
reportWorker.on('completed', (job, result) => {
console.log(`[WORKER] Job ${job.id} completed:`, result);
});
reportWorker.on('failed', (job, error) => {
console.error(`[WORKER] Job ${job.id} failed permanently:`, error.message);
// Send alert to admin or retry manually
});
reportWorker.on('progress', (job, progress) => {
console.log(`[WORKER] Job ${job.id} progress: ${progress}%`);
});
export default reportWorker;
Progress Tracking from ChatGPT
Users can check job status via MCP tool:
// MCP Server tool: check-report-status
async function checkReportStatus(jobId) {
const job = await reportQueue.getJob(jobId);
if (!job) {
return { status: 'not-found' };
}
const state = await job.getState();
const progress = job.progress || 0;
return {
status: state, // 'waiting', 'active', 'completed', 'failed'
progress: `${progress}%`,
attempts: job.attemptsMade,
failedReason: job.failedReason,
result: state === 'completed' ? job.returnvalue : null,
};
}
This enables real-time progress updates within ChatGPT conversations without polling the MCP server.
Job Queue Monitoring
Production systems require visibility into job queue health, failed jobs, and performance metrics. Bull Board provides a web-based dashboard for BullMQ queues.
Installing Bull Board
npm install @bull-board/express @bull-board/api
Dashboard Setup
Add to your MCP server's Express app:
import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
import { ExpressAdapter } from '@bull-board/express';
import { reportQueue, emailQueue, cleanupQueue } from './queues/queueManager.js';
const serverAdapter = new ExpressAdapter();
serverAdapter.setBasePath('/admin/queues'); // Dashboard URL
createBullBoard({
queues: [
new BullMQAdapter(reportQueue),
new BullMQAdapter(emailQueue),
new BullMQAdapter(cleanupQueue),
],
serverAdapter,
});
app.use('/admin/queues', serverAdapter.getRouter());
Access the dashboard at http://localhost:3000/admin/queues to:
- View waiting, active, completed, and failed jobs
- Inspect job data and stack traces
- Manually retry failed jobs
- Clean up old jobs
- Monitor queue throughput
Performance Metrics
Track key metrics in your monitoring system (Prometheus, Datadog, etc.):
import { reportQueue } from './queues/queueManager.js';
setInterval(async () => {
const counts = await reportQueue.getJobCounts();
console.log('[METRICS] Queue stats:', {
waiting: counts.waiting,
active: counts.active,
completed: counts.completed,
failed: counts.failed,
delayed: counts.delayed,
});
// Send to monitoring system
metrics.gauge('queue.waiting', counts.waiting);
metrics.gauge('queue.active', counts.active);
metrics.gauge('queue.failed', counts.failed);
}, 60000); // Every minute
Set up alerts for:
- Failed job spike:
failed > 50in 5 minutes - Queue backup:
waiting > 1000for more than 10 minutes - Worker downtime:
active === 0when jobs are waiting - High retry rate:
attemptsMade > 2for more than 10% of jobs
Best Practices Summary
1. Job Idempotency
Ensure jobs can be retried safely without duplicate side effects. Use unique job IDs and check for existing results before processing.
2. Timeout Configuration
Set job timeouts to prevent hung workers:
await queue.add('long-task', data, {
timeout: 300000, // 5 minutes max
});
3. Dead Letter Queues
Move permanently failed jobs to a separate queue for manual inspection:
worker.on('failed', async (job, error) => {
if (job.attemptsMade >= job.opts.attempts) {
await deadLetterQueue.add('failed-job', {
originalJob: job.data,
error: error.message,
stack: error.stack,
});
}
});
4. Graceful Shutdown
Close workers cleanly on server shutdown:
process.on('SIGTERM', async () => {
console.log('[SHUTDOWN] Closing workers gracefully...');
await reportWorker.close();
await emailWorker.close();
process.exit(0);
});
Conclusion
Background jobs transform your MCP server from a simple request-response system into a robust, production-ready platform capable of handling complex, long-running tasks. By implementing BullMQ job queues, node-cron scheduling, progress tracking, and monitoring dashboards, you ensure ChatGPT users experience fast, responsive interactions even when processing heavy workloads.
Start with report generation or email campaigns, then expand to data synchronization, AI model fine-tuning, and scheduled maintenance. Proper background job architecture is the foundation of scalable ChatGPT applications that delight users and pass OpenAI's performance reviews.
For complete MCP server implementation details, see our MCP Server Development Complete Guide.
Related Resources
- BullMQ Documentation - Official BullMQ guide with advanced patterns
- node-cron Documentation - Cron syntax and examples
- Redis Documentation - Redis best practices for job queues
- Bull Board - Queue monitoring dashboard
Ready to implement background jobs in your ChatGPT app? Start building with MakeAIHQ's no-code MCP server generator and deploy production-ready background processing in minutes.