bc-webclient-mcp
Version:
Model Context Protocol (MCP) server for Microsoft Dynamics 365 Business Central via WebUI protocol. Enables AI assistants to interact with BC through the web client protocol, supporting Card, List, and Document pages with full line item support and server
135 lines • 4.04 kB
JavaScript
/**
* Audit Logger for MCP Tool Executions
*
* Logs all tool executions that required user consent.
* Provides audit trail for compliance and debugging.
*
* CRITICAL FIX: Logs result AFTER execution completes, not before.
* This prevents contradictory audit entries where "success" is logged
* but the operation actually fails.
*/
/**
* Expanded list of sensitive key patterns for redaction.
* Includes all common authentication and credential patterns.
*/
const SENSITIVE_KEY_PATTERNS = [
'password',
'passwd',
'pwd',
'token',
'secret',
'apikey',
'api_key',
'key',
'auth',
'authorization',
'bearer',
'client_secret',
'clientsecret',
'access_token',
'accesstoken',
'refresh_token',
'refreshtoken',
'private_key',
'privatekey',
'credential',
'credentials',
];
export class AuditLogger {
logger;
maxEvents;
events = [];
constructor(logger, maxEvents = 1000) {
this.logger = logger;
this.maxEvents = maxEvents;
}
/**
* Log a tool execution that required user approval.
*
* NOTE: This should only be called AFTER the tool execution completes,
* with the actual result status (success/error).
*/
logToolExecution(event) {
const auditEvent = {
timestamp: new Date(),
...event,
};
// Add to in-memory buffer
this.events.push(auditEvent);
// Trim old events if buffer is full
if (this.events.length > this.maxEvents) {
this.events.shift();
}
// Log to structured logger
if (this.logger) {
const level = auditEvent.result === 'error' ? 'warn' : 'info';
this.logger[level]('Tool execution audit', {
toolName: auditEvent.toolName,
userApproved: auditEvent.userApproved,
result: auditEvent.result,
timestamp: auditEvent.timestamp.toISOString(),
inputSummary: this.sanitizeInput(auditEvent.inputSummary),
...(auditEvent.errorMessage && { error: auditEvent.errorMessage }),
...(auditEvent.userId && { userId: auditEvent.userId }),
});
}
}
/**
* Get recent audit events.
*/
getRecentEvents(count = 100) {
return this.events.slice(-count);
}
/**
* Sanitize input to remove sensitive data before logging.
*
* EXPANDED REDACTION: Now includes comprehensive list of credential patterns.
*/
sanitizeInput(input) {
const sanitized = {};
for (const [key, value] of Object.entries(input)) {
const lowerKey = key.toLowerCase();
// Check if key matches any sensitive pattern
const isSensitive = SENSITIVE_KEY_PATTERNS.some(pattern => lowerKey.includes(pattern));
if (isSensitive) {
sanitized[key] = '[REDACTED]';
}
// Keep other fields (truncate if too long)
else if (typeof value === 'string' && value.length > 100) {
sanitized[key] = value.substring(0, 100) + '...';
}
else if (typeof value === 'object' && value !== null) {
// Recursively sanitize nested objects
if (Array.isArray(value)) {
sanitized[key] = `[Array(${value.length})]`;
}
else {
sanitized[key] = this.sanitizeInput(value);
}
}
else {
sanitized[key] = value;
}
}
return sanitized;
}
/**
* Export audit log to JSON.
*/
exportToJSON() {
return JSON.stringify(this.events, null, 2);
}
/**
* Clear all audit events.
*/
clear() {
this.events.length = 0;
}
/**
* Get total event count.
*/
getEventCount() {
return this.events.length;
}
}
//# sourceMappingURL=audit-logger.js.map