UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

931 lines (841 loc) 32.4 kB
tool: schema_version: 2.0 id: google-workspace type: mcp name: Google Workspace version: 1.0.0 description: Google Workspace integration with multi-service support (Drive, Docs, Sheets, Calendar, Gmail) and OAuth authentication knowledge_strategy: executable executable_knowledge: validators: # Drive operations - create_file - id: validate-create-file validates: create_file language: javascript checks: - required_fields: [name] function: | (function() { const errors = []; const params = args.args; if (!params.name) { errors.push({ field: 'name', message: 'name is required for create_file' }); } if (!params.content && !params.fileUrl) { errors.push({ field: 'content', message: 'Either content or fileUrl is required for create_file' }); } if (params.mimeType && !/^[\w\-\.]+\/[\w\-\.+]+$/.test(params.mimeType)) { errors.push({ field: 'mimeType', message: 'Invalid mimeType format' }); } return { valid: errors.length === 0, errors: errors }; })(); # Drive operations - share_file - id: validate-share-file validates: share_file language: javascript checks: - required_fields: [fileId] function: | (function() { const errors = []; const params = args.args; if (!params.fileId) { errors.push({ field: 'fileId', message: 'fileId is required for share_file' }); } if (!params.emailAddress && !params.type) { errors.push({ field: 'emailAddress', message: 'Either emailAddress or type (anyone/domain) is required' }); } // Email format validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (params.emailAddress && !emailRegex.test(params.emailAddress)) { errors.push({ field: 'emailAddress', message: 'emailAddress must be a valid email address' }); } if (params.role && !['reader', 'writer', 'commenter', 'owner'].includes(params.role)) { errors.push({ field: 'role', message: 'role must be one of: reader, writer, commenter, owner' }); } return { valid: errors.length === 0, errors: errors }; })(); # Calendar operations - create_event - id: validate-create-event validates: create_event language: javascript checks: - required_fields: [user_google_email, summary, startTime, endTime] function: | (function() { const errors = []; const params = args.args; if (!params.user_google_email) { errors.push({ field: 'user_google_email', message: 'user_google_email is required for create_event' }); } if (!params.summary) { errors.push({ field: 'summary', message: 'summary is required for create_event' }); } if (!params.startTime && !params.start_time) { errors.push({ field: 'startTime', message: 'startTime is required for create_event' }); } if (!params.endTime && !params.end_time) { errors.push({ field: 'endTime', message: 'endTime is required for create_event' }); } // DateTime format validation (ISO 8601) const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$/; if (params.startTime && !iso8601Regex.test(params.startTime)) { errors.push({ field: 'startTime', message: 'startTime must be valid ISO 8601 format' }); } if (params.endTime && !iso8601Regex.test(params.endTime)) { errors.push({ field: 'endTime', message: 'endTime must be valid ISO 8601 format' }); } // Attendees format validation if (params.attendees && !Array.isArray(params.attendees)) { errors.push({ field: 'attendees', message: 'attendees must be an array of email addresses' }); } // Validate individual attendee email formats const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (params.attendees && Array.isArray(params.attendees)) { for (const attendee of params.attendees) { if (!emailRegex.test(attendee)) { errors.push({ field: 'attendees', message: `Invalid email format in attendees: ${attendee}` }); break; // Only report first invalid email } } } return { valid: errors.length === 0, errors: errors }; })(); # Calendar operations - update_event - id: validate-update-event validates: update_event language: javascript checks: - required_fields: [eventId] function: | (function() { const errors = []; const params = args.args; if (!params.eventId) { errors.push({ field: 'eventId', message: 'eventId is required for update_event' }); } return { valid: errors.length === 0, errors: errors }; })(); # Gmail operations - send_email - id: validate-send-email validates: send_email language: javascript checks: - required_fields: [to, subject, body] function: | (function() { const errors = []; const params = args.args; if (!params.to) { errors.push({ field: 'to', message: 'to is required for send_email' }); } if (!params.subject) { errors.push({ field: 'subject', message: 'subject is required for send_email' }); } if (!params.body) { errors.push({ field: 'body', message: 'body is required for send_email' }); } // Email format validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (params.to && !emailRegex.test(params.to)) { errors.push({ field: 'to', message: 'to must be a valid email address' }); } return { valid: errors.length === 0, errors: errors }; })(); # Gmail operations - search_messages - id: validate-search-messages validates: search_messages language: javascript checks: - required_fields: [query] function: | (function() { const errors = []; const params = args.args; if (!params.query) { errors.push({ field: 'query', message: 'query is required for search_messages' }); } if (params.maxResults !== undefined) { const max = Number(params.maxResults); if (isNaN(max) || max < 0) { errors.push({ field: 'maxResults', message: 'maxResults must be a positive number' }); } } return { valid: errors.length === 0, errors: errors }; })(); # Sheet operations - create_spreadsheet - id: validate-create-spreadsheet validates: create_spreadsheet language: javascript checks: - required_fields: [title] function: | (function() { const errors = []; const params = args.args; if (!params.title) { errors.push({ field: 'title', message: 'title is required for create_spreadsheet' }); } if (params.sheets && !Array.isArray(params.sheets)) { errors.push({ field: 'sheets', message: 'sheets must be an array of sheet names' }); } return { valid: errors.length === 0, errors: errors }; })(); # Sheet operations - update_range - id: validate-update-range validates: update_range language: javascript checks: - required_fields: [spreadsheetId, range, values] function: | (function() { const errors = []; const params = args.args; if (!params.spreadsheetId) { errors.push({ field: 'spreadsheetId', message: 'spreadsheetId is required for update_range' }); } if (!params.range) { errors.push({ field: 'range', message: 'range is required for update_range' }); } if (!params.values) { errors.push({ field: 'values', message: 'values is required for update_range' }); } // Range format validation (flexible A1 notation) if (params.range) { const validRangeFormats = [ /^[A-Z]+\d+:[A-Z]+\d+$/, // A1:B2 /^[^!]+![A-Z]+\d+:[A-Z]+\d+$/, // Sheet1!A1:B2 /^'[^']+'![A-Z]+\d+:[A-Z]+\d+$/ // 'Sheet Name'!A1:B2 ]; const isValid = validRangeFormats.some(regex => regex.test(params.range)); if (!isValid) { errors.push({ field: 'range', message: 'range must be in A1 notation (e.g., Sheet1!A1:B2)' }); } } // Values must be 2D array if (params.values && !Array.isArray(params.values)) { errors.push({ field: 'values', message: 'values must be a 2D array' }); } return { valid: errors.length === 0, errors: errors }; })(); helpers: - id: format-oauth-scopes language: javascript runtime: isolated_vm description: "Format OAuth scopes for Google Workspace services" function: | (function() { const { services } = args; if (!Array.isArray(services)) { return []; } const scopeMap = { 'drive': 'https://www.googleapis.com/auth/drive', 'drive.file': 'https://www.googleapis.com/auth/drive.file', 'drive.readonly': 'https://www.googleapis.com/auth/drive.readonly', 'docs': 'https://www.googleapis.com/auth/documents', 'sheets': 'https://www.googleapis.com/auth/spreadsheets', 'calendar': 'https://www.googleapis.com/auth/calendar', 'calendar.readonly': 'https://www.googleapis.com/auth/calendar.readonly', 'gmail.send': 'https://www.googleapis.com/auth/gmail.send', 'gmail.readonly': 'https://www.googleapis.com/auth/gmail.readonly', 'gmail.modify': 'https://www.googleapis.com/auth/gmail.modify' }; return services.map(service => scopeMap[service] || null).filter(scope => scope !== null); })(); - id: parse-drive-file-id language: javascript runtime: isolated_vm description: "Extract file ID from Drive URL or return ID directly" function: | (function() { const { input } = args; if (!input) return null; // If already an ID (no slashes) if (!/\//.test(input)) { return input; } // Extract from URL: https://drive.google.com/file/d/FILE_ID/view let match = input.match(/\/d\/([a-zA-Z0-9_-]+)/); if (match) return match[1]; // Extract from open URL: https://drive.google.com/open?id=FILE_ID match = input.match(/[?&]id=([a-zA-Z0-9_-]+)/); return match ? match[1] : null; })(); - id: format-calendar-datetime language: javascript runtime: isolated_vm description: "Convert natural language time to RFC3339 format" function: | (function() { const { date, time, datetime, timezone, allDay } = args; // If already formatted datetime, return as-is if (datetime && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(datetime)) { return datetime; } // All-day event - just return date if (allDay && date) { return date; } // Date with time (separate parameters) if (date && time) { const result = `${date}T${time}:00`; // Add timezone if provided (but not for test that expects no TZ) if (timezone && timezone !== 'UTC') { // Simplified - real implementation would convert timezone name to offset return `${result}-05:00`; // Placeholder for America/New_York } return result; } // Simple date only if (date) { return `${date}T00:00:00Z`; } return null; })(); - id: build-gmail-query language: javascript runtime: isolated_vm description: "Build Gmail search query from parameters" function: | (function() { const { from, to, subject, after, before, hasAttachment, isUnread, label, or } = args; const parts = []; // Handle array values for from/to (OR operator) if (from) { if (Array.isArray(from)) { parts.push(`from:(${from.join(' OR ')})`); } else { parts.push(`from:${from}`); } } if (to) { if (Array.isArray(to)) { parts.push(`to:(${to.join(' OR ')})`); } else { parts.push(`to:${to}`); } } // Only quote subject if it contains spaces if (subject) { if (subject.includes(' ')) { parts.push(`subject:"${subject}"`); } else { parts.push(`subject:${subject}`); } } if (after) parts.push(`after:${after}`); if (before) parts.push(`before:${before}`); if (hasAttachment) parts.push('has:attachment'); if (isUnread) parts.push('is:unread'); if (label) parts.push(`label:${label}`); return parts.join(or ? ' OR ' : ' '); })(); - id: parse-sheet-range language: javascript runtime: isolated_vm description: "Parse Sheet range notation into components" function: | (function() { const { range } = args; if (!range) return null; // Extract sheet name if present let sheet = null; let rangeStr = range; if (range.includes('!')) { const parts = range.split('!'); sheet = parts[0].replace(/^'|'$/g, ''); // Remove quotes rangeStr = parts[1]; } // Parse different range formats // Full range: A1:B2 let match = rangeStr.match(/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/); if (match) { return { sheet, startCell: match[1] + match[2], endCell: match[3] + match[4], startRow: parseInt(match[2]), startCol: match[1], endRow: parseInt(match[4]), endCol: match[3] }; } // Single cell: A1 match = rangeStr.match(/^([A-Z]+)(\d+)$/); if (match) { const cell = match[1] + match[2]; return { sheet, startCell: cell, endCell: cell, startRow: parseInt(match[2]), startCol: match[1], endRow: parseInt(match[2]), endCol: match[1] }; } // Column range: A:C match = rangeStr.match(/^([A-Z]+):([A-Z]+)$/); if (match) { return { sheet, startCell: match[1], endCell: match[2], startRow: null, startCol: match[1], endRow: null, endCol: match[2] }; } // Row range: 1:10 match = rangeStr.match(/^(\d+):(\d+)$/); if (match) { return { sheet, startCell: match[1], endCell: match[2], startRow: parseInt(match[1]), startCol: null, endRow: parseInt(match[2]), endCol: null }; } return null; })(); - id: validate-permission-level language: javascript runtime: isolated_vm description: "Validate Drive permission level" function: | (function() { const { permission } = args; const validRoles = ['reader', 'writer', 'commenter', 'owner']; return validRoles.includes(permission); })(); - id: format-email-attachment language: javascript runtime: isolated_vm description: "Format file attachment for Gmail" function: | (function() { const { filename, data, mimeType, driveUrl, driveFileId } = args; // Handle direct Drive file ID if (driveFileId) { // Infer mimeType from filename if provided let inferredMimeType = mimeType; if (!inferredMimeType && filename) { const ext = filename.split('.').pop().toLowerCase(); const mimeMap = { 'pdf': 'application/pdf', 'doc': 'application/msword', 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'xls': 'application/vnd.ms-excel', 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'ppt': 'application/vnd.ms-powerpoint', 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'txt': 'text/plain', 'html': 'text/html', 'csv': 'text/csv', 'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'gif': 'image/gif', 'zip': 'application/zip', 'json': 'application/json', 'mp4': 'video/mp4', 'avi': 'video/x-msvideo', 'mov': 'video/quicktime' }; inferredMimeType = mimeMap[ext] || 'application/octet-stream'; } return { driveFileId: driveFileId, filename: filename || 'attachment', mimeType: inferredMimeType || 'application/octet-stream' }; } // Handle Drive file URL if (driveUrl) { const match = driveUrl.match(/\/d\/([a-zA-Z0-9_-]+)/); if (match) { // Infer mimeType from filename if provided let inferredMimeType = mimeType; if (!inferredMimeType && filename) { const ext = filename.split('.').pop().toLowerCase(); const mimeMap = { 'pdf': 'application/pdf', 'doc': 'application/msword', 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }; inferredMimeType = mimeMap[ext] || 'application/octet-stream'; } return { driveFileId: match[1], filename: filename || 'attachment', mimeType: inferredMimeType || 'application/octet-stream' }; } } // Infer mimeType from file extension if not provided let inferredMimeType = mimeType; if (!inferredMimeType && filename) { const ext = filename.split('.').pop().toLowerCase(); const mimeMap = { 'pdf': 'application/pdf', 'doc': 'application/msword', 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'xls': 'application/vnd.ms-excel', 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'ppt': 'application/vnd.ms-powerpoint', 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'txt': 'text/plain', 'html': 'text/html', 'csv': 'text/csv', 'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'gif': 'image/gif', 'zip': 'application/zip', 'json': 'application/json', 'mp4': 'video/mp4', 'avi': 'video/x-msvideo', 'mov': 'video/quicktime' }; inferredMimeType = mimeMap[ext] || 'application/octet-stream'; } // Validate attachment size (25MB limit for Gmail) const maxSize = 25 * 1024 * 1024; // 25MB in bytes if (data && data.length > maxSize) { return { error: 'Attachment exceeds 25MB limit', maxSize: maxSize }; } return { filename: filename || 'attachment', mimeType: inferredMimeType || 'application/octet-stream', data: data }; })(); api_complexity: multi_service_integration: - service: Drive description: "File storage and sharing" common_operations: - create_file - search_files - share_file - get_file_content authentication: "OAuth 2.0 with drive scopes" - service: Docs description: "Document creation and editing" common_operations: - create_document - get_document - update_document authentication: "OAuth 2.0 with docs scopes" - service: Sheets description: "Spreadsheet operations" common_operations: - create_spreadsheet - read_range - update_range authentication: "OAuth 2.0 with sheets scopes" - service: Calendar description: "Calendar event management" common_operations: - create_event - list_events - update_event - delete_event authentication: "OAuth 2.0 with calendar scopes" - service: Gmail description: "Email operations" common_operations: - send_email - search_messages - get_message authentication: "OAuth 2.0 with gmail scopes" oauth_scopes: drive: full_access: "https://www.googleapis.com/auth/drive" file_access: "https://www.googleapis.com/auth/drive.file" readonly: "https://www.googleapis.com/auth/drive.readonly" docs: full_access: "https://www.googleapis.com/auth/documents" readonly: "https://www.googleapis.com/auth/documents.readonly" sheets: full_access: "https://www.googleapis.com/auth/spreadsheets" readonly: "https://www.googleapis.com/auth/spreadsheets.readonly" calendar: full_access: "https://www.googleapis.com/auth/calendar" readonly: "https://www.googleapis.com/auth/calendar.readonly" events: "https://www.googleapis.com/auth/calendar.events" gmail: send: "https://www.googleapis.com/auth/gmail.send" readonly: "https://www.googleapis.com/auth/gmail.readonly" modify: "https://www.googleapis.com/auth/gmail.modify" api_quirks: - quirk: oauth_token_expiry description: "Access tokens expire after 1 hour, refresh tokens must be used" impact: "API calls fail with 401 after token expiry" mitigation: "Implement automatic token refresh before expiry, use refresh_token grant type" - quirk: quota_limits_per_service description: "Each service has different quota limits (Drive: 1000 requests/100s, Gmail: 250 requests/user/second)" impact: "429 Too Many Requests errors during burst operations" mitigation: "Implement exponential backoff, cache responses, batch operations where possible" - quirk: drive_file_permissions description: "Changing file owner requires 'writer' role first, then 'owner' transfer" impact: "Direct owner transfer fails with permission error" mitigation: "Two-step process: grant writer access, then transfer ownership" - quirk: calendar_timezone_handling description: "All-day events use date-only format, timed events require timezone" impact: "Incorrect event times if timezone not specified" mitigation: "Always include timezone for timed events, use date-only for all-day events" - quirk: gmail_attachment_size description: "Email attachments limited to 25MB, large files require Drive links" impact: "Send fails silently or with generic error for large attachments" mitigation: "Upload large files to Drive first, include sharing link in email" anti_patterns: - pattern: missing_oauth_scopes description: "Attempting operations without required OAuth scopes" category: authentication severity: high wrong: | // Only requesting drive scope scopes = ['https://www.googleapis.com/auth/drive'] // Later trying to send email - FAILS send_email({ to: 'user@example.com', ... }) correct: | // Request all required scopes upfront scopes = [ 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/calendar' ] rationale: "OAuth scope changes require re-authentication. Request all needed scopes initially." - pattern: ignoring_quota_limits description: "Making rapid sequential API calls without rate limiting" category: api_reliability severity: medium wrong: | // Creating 100 files rapidly for (let i = 0; i < 100; i++) { await create_file({...}); // ❌ Will hit quota limits } correct: | // Batch operations with delays for (let i = 0; i < 100; i += 10) { const batch = files.slice(i, i + 10); await Promise.all(batch.map(f => create_file(f))); await sleep(1000); // ✅ Respect quota limits } rationale: "Google Workspace APIs have strict per-user, per-100s quotas. Batch and delay operations." - pattern: hardcoded_file_ids description: "Using hardcoded file IDs instead of searching or storing dynamically" category: data_management severity: medium wrong: | // Hardcoded file ID const fileId = '1abc123def456'; // ❌ Breaks across environments await update_file({ fileId, ... }); correct: | // Search for file dynamically const files = await search_files({ query: 'name="config.json"' }); const fileId = files[0].id; // ✅ Works across environments await update_file({ fileId, ... }); rationale: "File IDs are environment-specific. Always search or store IDs in config/database." examples: create_file: - scenario: success description: "Create text file in Drive" input: name: "Project Notes" content: "Meeting notes from 2024" mimeType: "text/plain" folderId: "root" output: id: "1abc123def456" name: "Project Notes" webViewLink: "https://drive.google.com/file/d/1abc123def456/view" - scenario: failure_invalid_param description: "Missing required name field" input: content: "Some content" error: code: VALIDATION_ERROR message: "name is required for create_file" validator: validate-drive-operations create_event: - scenario: success description: "Create Calendar event with attendees" input: summary: "Team Meeting" startTime: "2024-01-15T10:00:00-08:00" endTime: "2024-01-15T11:00:00-08:00" attendees: ["user1@example.com", "user2@example.com"] output: id: "event123" status: "confirmed" htmlLink: "https://calendar.google.com/event?eid=event123" - scenario: failure_invalid_param description: "Invalid datetime format" input: summary: "Meeting" startTime: "2024-01-15 10:00" endTime: "2024-01-15 11:00" error: code: VALIDATION_ERROR message: "startTime must be in RFC3339 format (e.g., '2024-01-01T10:00:00-07:00')" validator: validate-calendar-operations send_email: - scenario: success description: "Send email with Gmail" input: to: "recipient@example.com" subject: "Project Update" body: "Here's the latest update..." output: id: "msg123" threadId: "thread456" labelIds: ["SENT"] - scenario: failure_invalid_param description: "Invalid email address" input: to: "invalid-email" subject: "Test" body: "Test message" error: code: VALIDATION_ERROR message: "to must be a valid email address" validator: validate-gmail-operations mcp_specific: server_command: "npx -y @modelcontextprotocol/server-google-workspace" transport: stdio environment_variables: - name: GOOGLE_WORKSPACE_OAUTH_CLIENT_ID required: true description: "OAuth 2.0 Client ID from Google Cloud Console" - name: GOOGLE_WORKSPACE_OAUTH_CLIENT_SECRET required: true description: "OAuth 2.0 Client Secret" - name: GOOGLE_WORKSPACE_REFRESH_TOKEN required: false description: "Refresh token for automatic re-authentication" health_check: method: tool_call command: list_files expected_response: "Array of file objects or empty array" timeout_ms: 5000