passgage-mcp-server
Version:
Model Context Protocol (MCP) server for Passgage Public API integration
412 lines • 17.8 kB
JavaScript
export function createSpecializedTools() {
return [
{
name: 'passgage_upload_file',
description: 'Upload a file to Passgage using presigned URL',
inputSchema: {
type: 'object',
properties: {
filename: {
type: 'string',
description: 'Name of the file to upload'
},
content_type: {
type: 'string',
description: 'MIME type of the file (e.g., image/jpeg, application/pdf)'
},
file_data: {
type: 'string',
description: 'Base64 encoded file data or file path'
},
purpose: {
type: 'string',
description: 'Purpose of the file upload (optional)',
enum: ['profile_photo', 'document', 'attachment', 'report']
}
},
required: ['filename', 'content_type', 'file_data']
}
},
{
name: 'passgage_approve_request',
description: 'Approve or reject a pending approval request',
inputSchema: {
type: 'object',
properties: {
approval_id: {
type: 'string',
description: 'UUID of the approval request'
},
action: {
type: 'string',
description: 'Action to take on the approval',
enum: ['approve', 'reject']
},
notes: {
type: 'string',
description: 'Optional notes for the approval decision'
}
},
required: ['approval_id', 'action']
}
},
{
name: 'passgage_bulk_approve',
description: 'Approve or reject multiple approval requests at once',
inputSchema: {
type: 'object',
properties: {
approval_ids: {
type: 'array',
items: { type: 'string' },
description: 'Array of approval request UUIDs'
},
action: {
type: 'string',
description: 'Action to take on all approvals',
enum: ['approve', 'reject']
},
notes: {
type: 'string',
description: 'Optional notes for all approval decisions'
}
},
required: ['approval_ids', 'action']
}
},
{
name: 'passgage_assign_user_to_shift',
description: 'Assign a user to a specific shift',
inputSchema: {
type: 'object',
properties: {
user_id: {
type: 'string',
description: 'UUID of the user to assign'
},
shift_id: {
type: 'string',
description: 'UUID of the shift'
},
date: {
type: 'string',
format: 'date',
description: 'Date for the shift assignment (YYYY-MM-DD)'
},
branch_id: {
type: 'string',
description: 'UUID of the branch (optional)'
}
},
required: ['user_id', 'shift_id', 'date']
}
},
{
name: 'passgage_track_entrance',
description: 'Record an entrance/exit event for a user',
inputSchema: {
type: 'object',
properties: {
user_id: {
type: 'string',
description: 'UUID of the user'
},
device_id: {
type: 'string',
description: 'UUID of the device recording the entrance'
},
entrance_type: {
type: 'string',
description: 'Type of entrance event',
enum: ['entry', 'exit']
},
timestamp: {
type: 'string',
format: 'date-time',
description: 'Timestamp of the entrance (ISO 8601 format)'
}
},
required: ['user_id', 'device_id', 'entrance_type']
}
},
{
name: 'passgage_search',
description: 'Universal search across multiple Passgage resources',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search term to look for'
},
resources: {
type: 'array',
items: {
type: 'string',
enum: ['users', 'branches', 'departments', 'devices', 'leaves', 'approvals']
},
description: 'List of resources to search in (default: all)'
},
limit: {
type: 'number',
description: 'Maximum number of results per resource type (max: 25)',
minimum: 1,
maximum: 25
}
},
required: ['query']
}
},
{
name: 'passgage_export_data',
description: 'Export data in various formats (CSV, JSON, Excel)',
inputSchema: {
type: 'object',
properties: {
resource: {
type: 'string',
description: 'Resource type to export',
enum: ['users', 'leaves', 'entrances', 'payrolls', 'approvals']
},
format: {
type: 'string',
description: 'Export format',
enum: ['csv', 'json', 'excel']
},
date_range: {
type: 'object',
properties: {
start_date: {
type: 'string',
format: 'date',
description: 'Start date for data export (YYYY-MM-DD)'
},
end_date: {
type: 'string',
format: 'date',
description: 'End date for data export (YYYY-MM-DD)'
}
}
},
filters: {
type: 'object',
description: 'Additional filters to apply',
additionalProperties: true
}
},
required: ['resource', 'format']
}
},
{
name: 'passgage_get_dashboard_stats',
description: 'Get dashboard statistics and key metrics',
inputSchema: {
type: 'object',
properties: {
date_range: {
type: 'string',
description: 'Time period for statistics',
enum: ['today', 'week', 'month', 'quarter', 'year']
},
metrics: {
type: 'array',
items: {
type: 'string',
enum: ['active_users', 'pending_approvals', 'total_leaves', 'entrances_today', 'late_arrivals']
},
description: 'Specific metrics to retrieve (default: all)'
}
}
}
}
];
}
function checkSpecializedToolPermission(toolName, client) {
const authMode = client.getAuthMode();
if (authMode === 'none') {
return { allowed: false, reason: 'Not authenticated. Please login or set API key first.' };
}
// Define permission rules for specialized tools
const specializedPermissions = {
'passgage_upload_file': { companyMode: true, userMode: true, description: 'File upload available in both modes' },
'passgage_approve_request': { companyMode: true, userMode: false, description: 'Approving requests requires admin privileges' },
'passgage_bulk_approve': { companyMode: true, userMode: false, description: 'Bulk operations require admin privileges' },
'passgage_assign_user_to_shift': { companyMode: true, userMode: false, description: 'Assigning users requires admin privileges' },
'passgage_track_entrance': { companyMode: true, userMode: true, description: 'Entrance tracking available in both modes' },
'passgage_search': { companyMode: true, userMode: true, description: 'Search available in both modes' },
'passgage_export_data': { companyMode: true, userMode: false, description: 'Data export requires admin privileges' },
'passgage_get_dashboard_stats': { companyMode: true, userMode: true, description: 'Dashboard stats available in both modes' }
};
const permission = specializedPermissions[toolName];
if (!permission) {
// Default: allow both modes for unknown tools
return { allowed: true };
}
const allowed = (authMode === 'company' && permission.companyMode) ||
(authMode === 'user' && permission.userMode);
if (!allowed) {
const reason = authMode === 'user'
? `This operation requires company-level access. ${permission.description ?? ''}`
: `This operation is not available in ${authMode} mode.`;
return { allowed: false, reason };
}
return { allowed: true };
}
export async function handleSpecializedTool(name, args, client) {
// Check authentication and permissions
const permissionCheck = checkSpecializedToolPermission(name, client);
if (!permissionCheck.allowed) {
throw new Error(permissionCheck.reason ?? 'Access denied');
}
try {
switch (name) {
case 'passgage_upload_file': {
// First request presigned URL
const uploadRequest = await client.post('/api/public/v1/file_uploads', {
filename: args.filename,
content_type: args.content_type,
purpose: args.purpose
});
return {
success: uploadRequest.success,
message: 'File upload initiated',
data: uploadRequest.data,
// Note: In a real implementation, you would upload the file to the presigned URL
upload_url: uploadRequest.data && uploadRequest.data.presigned_url ? uploadRequest.data.presigned_url : undefined,
file_id: uploadRequest.data && uploadRequest.data.id ? uploadRequest.data.id : undefined
};
}
case 'passgage_approve_request': {
const endpoint = `/api/public/v1/approvals/${args.approval_id}/${args.action}`;
const result = await client.post(endpoint, {
notes: args.notes
});
return {
success: result.success,
message: `Request ${args.action}d successfully`,
data: result.data
};
}
case 'passgage_bulk_approve': {
const results = [];
for (const approvalId of args.approval_ids) {
try {
const endpoint = `/api/public/v1/approvals/${approvalId}/${args.action}`;
const result = await client.post(endpoint, {
notes: args.notes
});
results.push({ id: approvalId, success: true, data: result.data });
}
catch (error) {
results.push({ id: approvalId, success: false, error: error.message });
}
}
const successCount = results.filter(r => r.success).length;
return {
success: successCount > 0,
message: `${successCount}/${args.approval_ids.length} approvals processed successfully`,
results
};
}
case 'passgage_assign_user_to_shift': {
const result = await client.post('/api/public/v1/user_shifts', {
user_id: args.user_id,
shift_id: args.shift_id,
date: args.date,
branch_id: args.branch_id
});
return {
success: result.success,
message: 'User assigned to shift successfully',
data: result.data
};
}
case 'passgage_track_entrance': {
const result = await client.post('/api/public/v1/entrances', {
user_id: args.user_id,
device_id: args.device_id,
entrance_type: args.entrance_type,
timestamp: args.timestamp ?? new Date().toISOString()
});
return {
success: result.success,
message: `${args.entrance_type} recorded successfully`,
data: result.data
};
}
case 'passgage_search': {
const resources = args.resources ?? ['users', 'branches', 'departments', 'devices', 'leaves', 'approvals'];
const limit = Math.min(args.limit ?? 10, 25);
const searchResults = {};
for (const resource of resources) {
try {
const result = await client.get(`/api/public/v1/${resource}`, {
per_page: limit,
q: {
name_cont: args.query,
email_cont: args.query,
description_cont: args.query
}
});
searchResults[resource] = result.data ?? [];
}
catch (error) {
searchResults[resource] = { error: error.message };
}
}
const totalResults = Object.values(searchResults)
.filter(results => Array.isArray(results))
.reduce((sum, results) => sum + results.length, 0);
return {
success: true,
message: `Found ${totalResults} results for "${args.query}"`,
query: args.query,
results: searchResults,
total_count: totalResults
};
}
case 'passgage_export_data': {
const queryParams = {
format: args.format,
...args.filters
};
if (args.date_range) {
queryParams['q[created_at_gteq]'] = args.date_range.start_date;
queryParams['q[created_at_lteq]'] = args.date_range.end_date;
}
const result = await client.get(`/api/public/v1/${args.resource}/export`, queryParams);
return {
success: result.success,
message: `${args.resource} data exported successfully`,
format: args.format,
data: result.data
};
}
case 'passgage_get_dashboard_stats': {
const metrics = args.metrics ?? ['active_users', 'pending_approvals', 'total_leaves', 'entrances_today', 'late_arrivals'];
const dateRange = args.date_range ?? 'today';
const queryParams = {
date_range: dateRange,
metrics: metrics.join(',')
};
const result = await client.get('/api/public/v1/dashboard/stats', queryParams);
return {
success: result.success,
message: 'Dashboard statistics retrieved successfully',
date_range: dateRange,
data: result.data
};
}
default:
throw new Error(`Unknown specialized tool: ${name}`);
}
}
catch (error) {
return {
success: false,
error: error.message,
tool: name
};
}
}
//# sourceMappingURL=specialized.js.map