We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/takumi0706/google-calendar-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
/**
* Security sanitizer for error messages and logging
* Prevents sensitive information leakage in error responses and logs
*/
import { ErrorCode } from './error-codes';
/**
* Sensitive data patterns that should be redacted
*/
const SENSITIVE_PATTERNS = [
// API Keys and tokens
/\b[A-Za-z0-9]{32,}\b/g, // Generic API keys
/\bsk-[A-Za-z0-9]{48,}\b/g, // OpenAI API keys
/\bAIza[A-Za-z0-9-_]{35}\b/g, // Google API keys
/\byaA[A-Za-z0-9-_]{40,}\b/g, // OAuth tokens
/\b[A-Za-z0-9+/]{40,}={0,2}\b/g, // Base64 encoded tokens
// Authentication tokens
/\bBearer\s+[A-Za-z0-9+/=]+/gi,
/\baccess_token['":\s]*[A-Za-z0-9+/=]+/gi,
/\brefresh_token['":\s]*[A-Za-z0-9+/=]+/gi,
/\bauthorization['":\s]*[A-Za-z0-9+/=\s]+/gi,
// Passwords and secrets
/\bpassword['":\s]*[^\s'"]+/gi,
/\bsecret['":\s]*[^\s'"]+/gi,
/\bprivate_key['":\s]*[^\s'"]+/gi,
// Email addresses (partial redaction)
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
// File paths that might contain usernames
/\b\/[Uu]sers\/[^/\s]+/g,
/\b[A-Z]:\\[Uu]sers\\[^\\\s]+/g,
];
/**
* Sensitive field names that should be redacted
*/
const SENSITIVE_FIELDS = [
'password',
'secret',
'token',
'key',
'authorization',
'auth',
'credentials',
'access_token',
'refresh_token',
'client_secret',
'private_key',
'session',
'cookie',
'jwt',
'signature'
];
/**
* Production-safe error messages for different error types
*/
const SAFE_ERROR_MESSAGES = {
[ErrorCode.AUTHENTICATION_ERROR]: 'Authentication failed. Please check your credentials.',
[ErrorCode.VALIDATION_ERROR]: 'Invalid input data provided.',
[ErrorCode.API_ERROR]: 'External service error occurred.',
[ErrorCode.SERVER_ERROR]: 'Internal server error occurred.',
[ErrorCode.CALENDAR_ERROR]: 'Calendar service error occurred.',
[ErrorCode.CONFIGURATION_ERROR]: 'Configuration error detected.',
[ErrorCode.PERMISSION_ERROR]: 'Access denied.',
[ErrorCode.NOT_FOUND_ERROR]: 'Requested resource not found.',
[ErrorCode.RATE_LIMIT_ERROR]: 'Rate limit exceeded.',
[ErrorCode.TOKEN_ERROR]: 'Token error occurred.'
};
/**
* Sanitize text by removing or redacting sensitive information
*/
export function sanitizeText(text: string): string {
if (!text || typeof text !== 'string') {
return text;
}
let sanitized = text;
// Apply pattern-based redaction
SENSITIVE_PATTERNS.forEach(pattern => {
sanitized = sanitized.replace(pattern, '[REDACTED]');
});
return sanitized;
}
/**
* Sanitize object by removing or redacting sensitive fields
*/
export function sanitizeObject(obj: unknown): unknown {
if (!obj || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(sanitizeObject);
}
const sanitized: Record<string, unknown> = {};
const objRecord = obj as Record<string, unknown>;
Object.entries(objRecord).forEach(([key, value]) => {
const lowerKey = key.toLowerCase();
// Check if field name is sensitive
const isSensitiveField = SENSITIVE_FIELDS.some(field =>
lowerKey.includes(field.toLowerCase())
);
if (isSensitiveField) {
sanitized[key] = '[REDACTED]';
} else if (typeof value === 'string') {
sanitized[key] = sanitizeText(value);
} else if (typeof value === 'object') {
sanitized[key] = sanitizeObject(value);
} else {
sanitized[key] = value;
}
});
return sanitized;
}
/**
* Get production-safe error message
*/
export function getProductionSafeErrorMessage(errorCode: ErrorCode, originalMessage?: string): string {
const isProduction = process.env.NODE_ENV === 'production';
if (!isProduction && originalMessage) {
return sanitizeText(originalMessage);
}
return SAFE_ERROR_MESSAGES[errorCode] || 'An error occurred.';
}
/**
* Sanitize error details for logging
*/
export function sanitizeErrorForLogging(error: unknown): unknown {
if (error instanceof Error) {
const baseErrorInfo = {
name: error.name,
message: sanitizeText(error.message),
stack: process.env.NODE_ENV === 'production' ? '[REDACTED]' : sanitizeText(error.stack || '')
};
const sanitizedError = sanitizeObject(error) as Record<string, unknown>;
return sanitizeObject({ ...baseErrorInfo, ...sanitizedError });
}
return sanitizeObject(error);
}
/**
* Sanitize request context for logging
*/
export function sanitizeRequestContext(context: {
path?: string;
method?: string;
url?: string;
headers?: Record<string, unknown>;
[key: string]: unknown;
}): Record<string, unknown> {
return sanitizeObject({
path: context.path,
method: context.method,
url: context.url ? sanitizeText(context.url) : undefined,
headers: sanitizeObject(context.headers || {}),
// Only include safe context fields
userAgent: typeof context.userAgent === 'string' ? sanitizeText(context.userAgent) : undefined,
timestamp: context.timestamp,
requestId: context.requestId
}) as Record<string, unknown>;
}
/**
* Check if current environment allows detailed error information
*/
export function shouldShowDetailedErrors(): boolean {
return process.env.NODE_ENV !== 'production';
}
/**
* Redact email addresses (show only first letter and domain)
*/
export function redactEmail(email: string): string {
if (!email || typeof email !== 'string' || !email.includes('@')) {
return email;
}
const [localPart, domain] = email.split('@');
if (localPart.length <= 1) {
return `${localPart}@${domain}`;
}
return `${localPart[0]}***@${domain}`;
}
/**
* Redact file paths to remove sensitive directory information
*/
export function redactFilePath(path: string): string {
if (!path || typeof path !== 'string') {
return path;
}
// Replace user directories with generic placeholder
return path
.replace(/\/Users\/[^/]+/g, '/Users/[USER]')
.replace(/C:\\Users\\[^\\]+/g, 'C:\\Users\\[USER]')
.replace(/\/home\/[^/]+/g, '/home/[USER]');
}
/**
* Create security-aware error response
*/
export function createSecureErrorResponse(
errorCode: ErrorCode,
originalMessage: string,
details?: unknown
): {
code: ErrorCode;
message: string;
details?: unknown;
} {
const isProduction = process.env.NODE_ENV === 'production';
const result: {
code: ErrorCode;
message: string;
details?: unknown;
} = {
code: errorCode,
message: getProductionSafeErrorMessage(errorCode, originalMessage)
};
if (!isProduction) {
result.details = sanitizeObject(details);
}
return result;
}