Skip to main content
Glama
handleGetWhereUsed.tsâ€ĸ17.5 kB
import { McpError, ErrorCode } from '../lib/utils'; import { makeAdtRequestWithTimeout, return_error, getBaseUrl, encodeSapObjectName } from '../lib/utils'; import { objectsListCache } from '../lib/getObjectsListCache'; export const TOOL_DEFINITION = { "name": "GetWhereUsed", "description": "Retrieve where-used references for ABAP objects via ADT usageReferences.", "inputSchema": { "type": "object", "properties": { "object_name": { "type": "string", "description": "Name of the ABAP object" }, "object_type": { "type": "string", "description": "Type of the ABAP object" }, "detailed": { "type": "boolean", "description": "If true, returns all references including packages and internal components.", "default": false } }, "required": [ "object_name", "object_type" ] } } as const; interface WhereUsedReference { name: string; type: string; uri: string; parentUri?: string; isResult?: boolean; canHaveChildren?: boolean; usageInformation?: string; objectIdentifier?: string; originalName?: string; // For storing original name when resolved } interface WhereUsedArgs { object_name: string; object_type: string; // Now supports any ADT object type, e.g. 'class', 'program', 'table', 'bdef', etc. detailed?: boolean; } /** * Builds the URI for the object based on its type and name. * * Supported object_type values: * - 'class' → ABAP class * - 'program' → ABAP program * - 'include' → ABAP include * - 'function' → ABAP function group * - 'interface' → ABAP interface * - 'package' → ABAP package * - 'table'/'TABL' → ABAP table (DDIC) * - 'bdef'/'BDEF' → ABAP Behavior Definition * * You can extend this list by adding new cases for other ADT object types (e.g. CDS, DT, etc.). * * If an unsupported object_type is provided, an error will be thrown. */ function buildObjectUri(objectName: string, objectType: string): string { const encodedName = encodeSapObjectName(objectName); switch (objectType) { case 'class': return `/sap/bc/adt/oo/classes/${encodedName}`; case 'program': return `/sap/bc/adt/programs/programs/${encodedName}`; case 'include': return `/sap/bc/adt/programs/includes/${encodedName}`; case 'function': return `/sap/bc/adt/functions/groups/${encodedName}`; case 'functionmodule': case 'function_module': case 'function-module': case 'fm': case 'fugr/ff': // objectName: GROUP|FM_NAME if (objectName.includes('|')) { const [group, fm] = objectName.split('|'); return `/sap/bc/adt/functions/groups/${encodeSapObjectName(group)}/fmodules/${encodeSapObjectName(fm)}`; } throw new McpError(ErrorCode.InvalidParams, 'Function module name must be in format GROUP|FM_NAME'); case 'interface': return `/sap/bc/adt/oo/interfaces/${encodedName}`; case 'badi': case 'badi_class': case 'badi-interface': case 'badi-implementation': return `/sap/bc/adt/oo/interfaces/${encodedName}`; case 'badi_class_impl': case 'badi-class-impl': return `/sap/bc/adt/oo/classes/${encodedName}`; case 'package': return `/sap/bc/adt/packages/${encodedName}`; case 'table': case 'tabl': case 'TABL': return `/sap/bc/adt/ddic/tables/${encodedName}`; case 'dataelement': case 'data_element': case 'data-element': return `/sap/bc/adt/ddic/dataelements/${encodedName}`; case 'domain': return `/sap/bc/adt/ddic/domains/${encodedName}`; case 'view': case 'ddicview': case 'cdsview': return `/sap/bc/adt/ddic/views/${encodedName}`; case 'searchhelp': case 'search_help': case 'search-help': return `/sap/bc/adt/ddic/searchhelps/${encodedName}`; case 'messageclass': case 'message_class': case 'message-class': return `/sap/bc/adt/ddic/messageclasses/${encodedName}`; case 'transaction': return `/sap/bc/adt/transactions/${encodedName}`; case 'bdef': case 'BDEF': return `/sap/bc/adt/bo/behaviordefinitions/${encodedName}`; case 'enhancementspot': case 'enhancement_spot': case 'enhancement-spot': case 'enhs': case 'enho': return `/sap/bc/adt/enhancements/enhsxsb/${encodedName}`; case 'enhancementimpl': case 'enhancement_impl': case 'enhancement-impl': case 'enhi': return `/sap/bc/adt/enhancements/enhoxhh/${encodedName}`; default: throw new McpError(ErrorCode.InvalidParams, `Unsupported object type: ${objectType}`); } } /** * Resolves object identifier to human-readable name using quickSearch API */ async function resolveObjectIdentifier(objectIdentifier: string): Promise<string | null> { try { const baseUrl = await getBaseUrl(); const query = encodeURIComponent(`${objectIdentifier}*`); const endpoint = `${baseUrl}/sap/bc/adt/repository/informationsystem/search?operation=quickSearch&query=${query}&maxResults=1`; const response = await makeAdtRequestWithTimeout(endpoint, 'GET', 'default'); // Parse XML response to extract description const descriptionMatch = response.data.match(/adtcore:description="([^"]*)"/); if (descriptionMatch) { return descriptionMatch[1]; } return null; } catch (error) { // If resolution fails, return null (we'll use original name) return null; } } /** * Checks if a reference is internal class structure (self-reference) */ function isInternalClassStructure(ref: WhereUsedReference): boolean { // Check objectIdentifier for self-references if (ref.objectIdentifier) { // Pattern: CL_BUS_ABSTRACT_MAIN_SCREEN===CP means it's the class itself // Pattern: CL_BUS_ABSTRACT_MAIN_SCREEN===CO means it's used somewhere else if (ref.objectIdentifier.includes('===CP') || ref.objectIdentifier.includes('===CI') || ref.objectIdentifier.includes('===CU')) { return true; // Internal structure } } // Check if parentUri points to the same class as the reference if (ref.parentUri && ref.uri) { const parentClass = ref.parentUri.match(/\/classes\/([^\/]+)/)?.[1]; const refClass = ref.uri.match(/\/classes\/([^\/]+)/)?.[1]; if (parentClass && refClass && parentClass === refClass) { // Additional check: if it's a method/attribute within the same class if (ref.uri.includes('#type=CLAS%2FOM') || ref.uri.includes('#type=CLAS%2FOA')) { return true; // Internal method or attribute } } } return false; } /** * Enhances references with resolved names for better readability */ async function enhanceReferencesWithNames(references: WhereUsedReference[]): Promise<WhereUsedReference[]> { const enhancedReferences = [...references]; // Process references that have cryptic names but objectIdentifiers for (let i = 0; i < enhancedReferences.length; i++) { const ref = enhancedReferences[i]; // If name is empty or cryptic and we have objectIdentifier, try to resolve it if (ref.objectIdentifier && (!ref.name || ref.name.length === 0)) { const resolvedName = await resolveObjectIdentifier(ref.objectIdentifier); if (resolvedName) { enhancedReferences[i] = { ...ref, name: resolvedName, originalName: ref.name // Keep original for reference }; } } } return enhancedReferences; } /** * Filters references to show only the most relevant ones (excludes packages and internal components) */ function filterMinimalReferences(references: WhereUsedReference[]): WhereUsedReference[] { return references.filter(ref => { // PRIORITY 1: Always show enhancement implementations (most important for developers) if (ref.type === 'ENHO/XHH') { return true; } // PRIORITY 2: Show main results that are marked as important (but not packages) if (ref.isResult === true && ref.type !== 'DEVC/K') { return true; } // PRIORITY 3: Show function modules with direct usage (real implementations) if (ref.type === 'FUGR/FF' && ref.usageInformation && ref.usageInformation.includes('gradeDirect')) { return true; } // HIDE EVERYTHING ELSE IN MINIMAL MODE: // Hide ALL packages - they're organizational, not functional usage if (ref.type === 'DEVC/K') { return false; } // Hide internal class structure (self-references) if (isInternalClassStructure(ref)) { return false; } // Hide ALL class internal structure (sections, methods, attributes) if (ref.name === 'Public Section' || ref.name === 'Private Section' || ref.name === 'Protected Section') { return false; } // Hide ALL internal class components if (ref.type && ref.type.startsWith('CLAS/')) { return false; } // Hide ALL items with empty type (usually internal structure) if (!ref.type) { return false; } // Hide function groups (show only specific functions) if (ref.type === 'FUGR/F') { return false; } // Hide programs unless they're marked as main results if (ref.type === 'PROG/P' && !ref.isResult) { return false; } // Hide includes unless they're marked as main results if (ref.type === 'PROG/I' && !ref.isResult) { return false; } // Show only if it's a main result or enhancement return ref.isResult === true || ref.type === 'ENHO/XHH'; }); } /** * Parses XML response and extracts where-used references */ function parseWhereUsedResponse(xmlData: string): WhereUsedReference[] { const references: WhereUsedReference[] = []; try { // Simple XML parsing for usageReferences response // Look for referencedObject elements const objectMatches = xmlData.match(/<usageReferences:referencedObject[^>]*>(.*?)<\/usageReferences:referencedObject>/gs); if (objectMatches) { for (const objectMatch of objectMatches) { // Extract attributes from the referencedObject element const uriMatch = objectMatch.match(/uri="([^"]*)"/); const parentUriMatch = objectMatch.match(/parentUri="([^"]*)"/); const isResultMatch = objectMatch.match(/isResult="([^"]*)"/); const canHaveChildrenMatch = objectMatch.match(/canHaveChildren="([^"]*)"/); const usageInformationMatch = objectMatch.match(/usageInformation="([^"]*)"/); const uri = uriMatch ? uriMatch[1] : ''; const parentUri = parentUriMatch ? parentUriMatch[1] : undefined; const isResult = isResultMatch ? isResultMatch[1] === 'true' : undefined; const canHaveChildren = canHaveChildrenMatch ? canHaveChildrenMatch[1] === 'true' : undefined; const usageInformation = usageInformationMatch ? usageInformationMatch[1] : undefined; // Extract adtObject attributes const adtObjectMatch = objectMatch.match(/<usageReferences:adtObject[^>]*>/); let name = ''; let type = ''; if (adtObjectMatch) { const nameMatch = adtObjectMatch[0].match(/adtcore:name="([^"]*)"/); const typeMatch = adtObjectMatch[0].match(/adtcore:type="([^"]*)"/); name = nameMatch ? nameMatch[1] : ''; type = typeMatch ? typeMatch[1] : ''; } // Extract objectIdentifier if present const objectIdentifierMatch = objectMatch.match(/<objectIdentifier>([^<]*)<\/objectIdentifier>/); const objectIdentifier = objectIdentifierMatch ? objectIdentifierMatch[1] : undefined; const reference: WhereUsedReference = { name, type, uri }; if (parentUri) reference.parentUri = parentUri; if (isResult !== undefined) reference.isResult = isResult; if (canHaveChildren !== undefined) reference.canHaveChildren = canHaveChildren; if (usageInformation) reference.usageInformation = usageInformation; if (objectIdentifier) reference.objectIdentifier = objectIdentifier; references.push(reference); } } } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to parse XML response: ${error instanceof Error ? error.message : String(error)}`); } return references; } /** * Returns where-used references for ABAP objects. * * Supported object_type values: * - class * - program * - include * - function (function group) * - functionmodule (function module, format: GROUP|FM_NAME) * - interface * - badi, badi_class, badi-implementation * - package * - table, tabl, TABL * - dataelement * - domain * - view, ddicview, cdsview * - searchhelp * - messageclass * - transaction * - bdef, BDEF * - enhancementspot, enhs, enho * - enhancementimpl, enhi */ export async function handleGetWhereUsed(args: any) { try { // Validate required parameters if (!args?.object_name) { throw new McpError(ErrorCode.InvalidParams, 'Object name is required'); } if (!args?.object_type) { throw new McpError(ErrorCode.InvalidParams, 'Object type is required'); } // Accept any object_type, validation is now handled in buildObjectUri const typedArgs = args as WhereUsedArgs; // 1. Build the object URI const objectUri = buildObjectUri(typedArgs.object_name, typedArgs.object_type); // 2. Prepare the usageReferences request const baseUrl = await getBaseUrl(); const endpoint = `${baseUrl}/sap/bc/adt/repository/informationsystem/usageReferences?uri=${encodeURIComponent(objectUri)}`; const requestBody = '<?xml version="1.0" encoding="UTF-8"?><usagereferences:usageReferenceRequest xmlns:usagereferences="http://www.sap.com/adt/ris/usageReferences"><usagereferences:affectedObjects/></usagereferences:usageReferenceRequest>'; // 3. Make the POST request const response = await makeAdtRequestWithTimeout( endpoint, 'POST', 'default', requestBody ); // 5. Parse the XML response const allReferences = parseWhereUsedResponse(response.data); // 6. Filter references if detailed=false (default) const isDetailed = typedArgs.detailed === true; let references = allReferences; if (!isDetailed) { references = filterMinimalReferences(allReferences); } // 7. Format the response based on detailed mode let formattedReferences; if (isDetailed) { // Detailed mode: show all fields formattedReferences = references; } else { // Minimal mode: show only name and type formattedReferences = references.map(ref => ({ name: ref.name, type: ref.type })); } const formattedResponse = { object_name: typedArgs.object_name, object_type: typedArgs.object_type, object_uri: objectUri, detailed: isDetailed, total_references: references.length, total_found: allReferences.length, filtered_out: isDetailed ? 0 : allReferences.length - references.length, references: formattedReferences }; const result = { isError: false, content: [ { type: "json", json: formattedResponse, }, ], }; objectsListCache.setCache(result); return result; } catch (error) { // MCP-compliant error response: always return content[] with type "text" return { isError: true, content: [ { type: "text", text: `ADT error: ${String(error)}` } ] }; } }

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/fr0ster/mcp-abap-adt'

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