check_build_status
Retrieve recent Xcode Cloud build status from Slack notifications, including workflow name, duration, and timestamp. Filter by workflow for specific builds.
Instructions
Get the latest Xcode Cloud build status from Slack notifications. Returns recent build messages including status, workflow name, duration, and timestamp.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Number of recent build messages to retrieve (default: 5, max: 20) | |
| workflow | No | Filter by workflow name (e.g., 'Cuti-E-Admin', 'Nutri-E'). Case-insensitive partial match. |
Implementation Reference
- index.js:49-66 (registration)Registration of the check_build_status tool in the ListToolsRequestSchema handler, defining its name, description, and input schema.
{ name: "check_build_status", description: "Get the latest Xcode Cloud build status from Slack notifications. Returns recent build messages including status, workflow name, duration, and timestamp.", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of recent build messages to retrieve (default: 5, max: 20)", default: 5, }, workflow: { type: "string", description: "Filter by workflow name (e.g., 'Cuti-E-Admin', 'Nutri-E'). Case-insensitive partial match.", }, }, required: [], }, - index.js:52-66 (schema)Input schema for check_build_status: optional 'limit' (number, default 5, max 20) and optional 'workflow' (string for filtering by workflow name).
inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of recent build messages to retrieve (default: 5, max: 20)", default: 5, }, workflow: { type: "string", description: "Filter by workflow name (e.g., 'Cuti-E-Admin', 'Nutri-E'). Case-insensitive partial match.", }, }, required: [], }, - index.js:149-300 (handler)Handler implementation for check_build_status: fetches Slack channel history from the build channel, parses Xcode Cloud build notification messages to extract status (succeeded/failed/running/cancelled), workflow name, duration, and timestamp, with optional workflow filtering and limit control.
case "check_build_status": { if (!buildChannelId) { return { content: [ { type: "text", text: "Error: SLACK_BUILD_CHANNEL_ID not configured. Please set it in your MCP config.", }, ], }; } const workflowFilter = args?.workflow?.toLowerCase(); // Fetch more messages if filtering, to ensure we get enough matches const fetchLimit = workflowFilter ? Math.min((args?.limit || 5) * 4, 100) : Math.min(args?.limit || 5, 20); const result = await slack.conversations.history({ channel: buildChannelId, limit: fetchLimit, }); if (!result.messages || result.messages.length === 0) { return { content: [ { type: "text", text: "No build messages found in the channel.", }, ], }; } // Helper function to extract duration from text const extractDuration = (text, attachments) => { // Common patterns for duration in Xcode Cloud messages const patterns = [ /duration[:\s]+(\d+)\s*(?:min(?:ute)?s?)?(?:\s*(\d+)\s*(?:sec(?:ond)?s?)?)?/i, /took\s+(\d+)\s*(?:min(?:ute)?s?)?(?:\s*(\d+)\s*(?:sec(?:ond)?s?)?)?/i, /completed\s+in\s+(\d+)\s*(?:min(?:ute)?s?)?(?:\s*(\d+)\s*(?:sec(?:ond)?s?)?)?/i, /(\d+)\s*(?:min(?:ute)?s?)\s*(?:(\d+)\s*(?:sec(?:ond)?s?)?)?/i, /(\d+):(\d+)\s*(?:min)?/i, // MM:SS format ]; const allText = text + ' ' + (attachments?.map(a => `${a.title || ''} ${a.text || ''}`).join(' ') || ''); for (const pattern of patterns) { const match = allText.match(pattern); if (match) { const mins = parseInt(match[1]) || 0; const secs = parseInt(match[2]) || 0; const totalSeconds = mins * 60 + secs; return { minutes: mins, seconds: secs, totalSeconds, formatted: mins > 0 ? `${mins}m ${secs}s` : `${secs}s`, }; } } return null; }; // Helper function to extract workflow name const extractWorkflow = (text, attachments) => { // Look for workflow name in text or attachments const allText = text + ' ' + (attachments?.map(a => `${a.title || ''} ${a.text || ''}`).join(' ') || ''); // Common patterns: "Workflow: Name", title of attachment often contains workflow const patterns = [ /workflow[:\s]+([^\n\r,]+)/i, /^([A-Z][a-zA-Z0-9-]+(?:\s+[A-Z][a-zA-Z0-9-]+)*)\s+(?:build|workflow)/i, ]; for (const pattern of patterns) { const match = allText.match(pattern); if (match) { return match[1].trim(); } } // Check attachment titles - often contains workflow name if (attachments && attachments.length > 0) { const title = attachments[0].title; if (title && title.length > 0 && title.length < 50) { return title; } } return null; }; // Parse build messages - Xcode Cloud messages have specific format let buildMessages = result.messages.map((msg) => { const timestamp = new Date(parseFloat(msg.ts) * 1000).toISOString(); // Try to extract build status from message let status = "unknown"; const text = msg.text || ""; if (text.toLowerCase().includes("succeeded") || text.toLowerCase().includes("success")) { status = "succeeded"; } else if (text.toLowerCase().includes("failed") || text.toLowerCase().includes("failure")) { status = "failed"; } else if (text.toLowerCase().includes("started") || text.toLowerCase().includes("running")) { status = "running"; } else if (text.toLowerCase().includes("cancelled") || text.toLowerCase().includes("canceled")) { status = "cancelled"; } const workflow = extractWorkflow(text, msg.attachments); const duration = extractDuration(text, msg.attachments); return { timestamp, status, workflow, duration, text: text.substring(0, 500), // Truncate long messages attachments: msg.attachments?.map(a => ({ title: a.title, text: a.text?.substring(0, 200), color: a.color, })), }; }); // Apply workflow filter if specified if (workflowFilter) { buildMessages = buildMessages.filter((msg) => { const workflow = msg.workflow?.toLowerCase() || ''; const text = msg.text?.toLowerCase() || ''; const attachmentText = msg.attachments?.map(a => `${a.title || ''} ${a.text || ''}`).join(' ').toLowerCase() || ''; return workflow.includes(workflowFilter) || text.includes(workflowFilter) || attachmentText.includes(workflowFilter); }); } // Apply limit after filtering const limit = Math.min(args?.limit || 5, 20); buildMessages = buildMessages.slice(0, limit); return { content: [ { type: "text", text: JSON.stringify({ builds: buildMessages, filter: workflowFilter ? { workflow: args.workflow } : null, count: buildMessages.length, }, null, 2), }, ], }; } - index.js:181-208 (helper)Helper function extractDuration: parses duration text from Xcode Cloud build messages using multiple regex patterns to extract minutes and seconds.
const extractDuration = (text, attachments) => { // Common patterns for duration in Xcode Cloud messages const patterns = [ /duration[:\s]+(\d+)\s*(?:min(?:ute)?s?)?(?:\s*(\d+)\s*(?:sec(?:ond)?s?)?)?/i, /took\s+(\d+)\s*(?:min(?:ute)?s?)?(?:\s*(\d+)\s*(?:sec(?:ond)?s?)?)?/i, /completed\s+in\s+(\d+)\s*(?:min(?:ute)?s?)?(?:\s*(\d+)\s*(?:sec(?:ond)?s?)?)?/i, /(\d+)\s*(?:min(?:ute)?s?)\s*(?:(\d+)\s*(?:sec(?:ond)?s?)?)?/i, /(\d+):(\d+)\s*(?:min)?/i, // MM:SS format ]; const allText = text + ' ' + (attachments?.map(a => `${a.title || ''} ${a.text || ''}`).join(' ') || ''); for (const pattern of patterns) { const match = allText.match(pattern); if (match) { const mins = parseInt(match[1]) || 0; const secs = parseInt(match[2]) || 0; const totalSeconds = mins * 60 + secs; return { minutes: mins, seconds: secs, totalSeconds, formatted: mins > 0 ? `${mins}m ${secs}s` : `${secs}s`, }; } } return null; }; - index.js:211-237 (helper)Helper function extractWorkflow: parses workflow name from Xcode Cloud build messages using regex patterns and attachment titles.
const extractWorkflow = (text, attachments) => { // Look for workflow name in text or attachments const allText = text + ' ' + (attachments?.map(a => `${a.title || ''} ${a.text || ''}`).join(' ') || ''); // Common patterns: "Workflow: Name", title of attachment often contains workflow const patterns = [ /workflow[:\s]+([^\n\r,]+)/i, /^([A-Z][a-zA-Z0-9-]+(?:\s+[A-Z][a-zA-Z0-9-]+)*)\s+(?:build|workflow)/i, ]; for (const pattern of patterns) { const match = allText.match(pattern); if (match) { return match[1].trim(); } } // Check attachment titles - often contains workflow name if (attachments && attachments.length > 0) { const title = attachments[0].title; if (title && title.length > 0 && title.length < 50) { return title; } } return null; };