Skip to main content
Glama

SAP OData to MCP Server

by Raistlin82
mcp-compatibility.ts8.45 kB
/** * MCP Compatibility Module * Provides compatibility mode for generic MCP clients */ export interface CompatibilityOptions { /** * Enable simplified responses for legacy clients */ simplifiedResponses?: boolean; /** * Disable UI suggestions in responses */ disableUISuggestions?: boolean; /** * Use minimal formatting (no emoji, no markdown) */ minimalFormatting?: boolean; /** * Maximum response length (characters) */ maxResponseLength?: number; /** * List of client patterns that should use legacy/compatibility mode * All other clients receive full features by default */ legacyClients?: string[]; /** * Client identifier for custom handling */ clientId?: string; } export class MCPCompatibilityManager { private static instance: MCPCompatibilityManager; private options: CompatibilityOptions = {}; private constructor() { // Parse legacy clients list from environment (clients that need compatibility mode) const legacyClientsEnv = process.env.MCP_LEGACY_CLIENTS || 'cline,windsurf,inspector'; const legacyClients = legacyClientsEnv .split(',') .map(client => client.trim().toLowerCase()) .filter(client => client.length > 0); // Check environment for compatibility settings // Default behavior: Full features for ALL clients, except those in MCP_LEGACY_CLIENTS this.options = { simplifiedResponses: process.env.MCP_SIMPLIFIED_RESPONSES === 'true', disableUISuggestions: process.env.MCP_DISABLE_UI_SUGGESTIONS === 'true', minimalFormatting: process.env.MCP_MINIMAL_FORMATTING === 'true', maxResponseLength: process.env.MCP_MAX_RESPONSE_LENGTH ? parseInt(process.env.MCP_MAX_RESPONSE_LENGTH) : undefined, legacyClients, }; } public static getInstance(): MCPCompatibilityManager { if (!MCPCompatibilityManager.instance) { MCPCompatibilityManager.instance = new MCPCompatibilityManager(); } return MCPCompatibilityManager.instance; } /** * Detect client type from headers or connection info * Returns the first detected pattern or the full User-Agent for analysis */ public detectClient(headers?: Record<string, string>): string { // Get user agent (case insensitive) const userAgent = (headers?.['user-agent'] || headers?.['User-Agent'] || '').toLowerCase(); if (!userAgent) { return 'generic'; } // Return the most specific match found, or the full user agent for pattern matching if (userAgent.includes('claude')) { return 'claude'; } if (userAgent.includes('mcp-inspector')) { return 'inspector'; } if (userAgent.includes('cline')) { return 'cline'; } if (userAgent.includes('windsurf')) { return 'windsurf'; } // Return the user agent for contains matching with MCP_FULL_FEATURE_CLIENTS return userAgent; } /** * Check if client should receive full features (inverted logic) * Uses contains logic to match User-Agent strings */ public isFullFeatureClient(clientId?: string): boolean { if (!clientId) { // Default to full features for unknown clients return true; } const normalizedClientId = clientId.toLowerCase(); // Check if client is in legacy list (should receive compatibility mode) const isLegacyClient = this.options.legacyClients?.some(pattern => normalizedClientId.includes(pattern.toLowerCase()) ) || false; // Return opposite: if NOT legacy, then full features return !isLegacyClient; } /** * Check if UI suggestions should be included */ public shouldIncludeUISuggestions(clientId?: string): boolean { // If globally disabled, return false if (this.options.disableUISuggestions) { return false; } // If globally forced, return true if (process.env.MCP_FORCE_UI_SUGGESTIONS === 'true') { return true; } // For full-feature clients, include UI suggestions (ignore compatibility settings) return this.isFullFeatureClient(clientId); } /** * Format response based on client compatibility */ public formatResponse(response: any, clientId?: string): any { // Full-feature clients get unmodified responses (ignore compatibility settings) if (this.isFullFeatureClient(clientId) && !this.options.simplifiedResponses) { return response; } // Non-full-feature clients get compatibility processing let simplified = response; if (typeof response === 'object' && response !== null) { simplified = { ...response }; // Remove UI suggestions for non-full-feature clients (unless globally forced) if (!this.shouldIncludeUISuggestions(clientId)) { delete simplified.uiSuggestions; delete simplified.suggestions; delete simplified.recommendedTools; delete simplified.nextSteps; } // Apply minimal formatting for non-full-feature clients if (this.options.minimalFormatting || !this.isFullFeatureClient(clientId)) { if (simplified.description && simplified.description.length > 500) { simplified.description = simplified.description.substring(0, 497) + '...'; } // Remove emoji from strings if (simplified.message) { simplified.message = this.removeEmoji(simplified.message); } if (simplified.status) { simplified.status = this.removeEmoji(simplified.status); } } // Limit response size if (this.options.maxResponseLength) { const responseStr = JSON.stringify(simplified); if (responseStr.length > this.options.maxResponseLength) { simplified = { ...simplified, truncated: true, originalSize: responseStr.length, }; } } } return simplified; } /** * Remove emoji from text */ private removeEmoji(text: string): string { return text .replace( /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu, '' ) .trim(); } /** * Simplify tool descriptions based on client compatibility */ public simplifyToolDescription(description: string, clientId?: string): string { // Full-feature clients get full descriptions (unless globally configured otherwise) if (this.isFullFeatureClient(clientId) && !this.options.simplifiedResponses) { return description; } // Non-full-feature clients get simplified descriptions // Remove examples and complex formatting let simplified = description; // Remove code examples simplified = simplified.replace(/```[\s\S]*?```/g, ''); // Remove emoji simplified = this.removeEmoji(simplified); // Remove excessive newlines simplified = simplified.replace(/\n{3,}/g, '\n\n'); // Limit length if (simplified.length > 300) { simplified = simplified.substring(0, 297) + '...'; } return simplified.trim(); } /** * Get compatibility settings */ public getSettings(): CompatibilityOptions { return { ...this.options }; } /** * Update compatibility settings */ public updateSettings(options: Partial<CompatibilityOptions>): void { this.options = { ...this.options, ...options }; } } /** * Helper function to wrap tool responses */ export function wrapToolResponse(response: any, toolName: string, clientId?: string): any { const manager = MCPCompatibilityManager.getInstance(); // Full-feature clients get unwrapped responses (unless globally configured otherwise) const shouldWrapResponse = manager.getSettings().simplifiedResponses || !manager.isFullFeatureClient(clientId); if (shouldWrapResponse) { return { success: true, tool: toolName, data: manager.formatResponse(response, clientId), timestamp: new Date().toISOString(), }; } // Full response for full-feature clients return response; } /** * Middleware for detecting client type */ export function detectClientMiddleware(req: any, res: any, next: any): void { const manager = MCPCompatibilityManager.getInstance(); const clientId = manager.detectClient(req.headers); // Attach client info to request req.mcpClient = clientId; req.mcpCompatibility = manager.getSettings(); next(); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Raistlin82/btp-sap-odata-to-mcp-server-optimized'

If you have feedback or need assistance with the MCP directory API, please join our Discord server