Skip to main content
Glama
handleGetIncludesList.tsâ€ĸ9.21 kB
import { McpError, ErrorCode, AxiosResponse } from '../lib/utils'; import { fetchNodeStructure, return_error } from '../lib/utils'; import { writeResultToFile } from '../lib/writeResultToFile'; export const TOOL_DEFINITION = { "name": "GetIncludesList", "description": "Recursively discover and list ALL include files within an ABAP program or include.", "inputSchema": { "type": "object", "properties": { "object_name": { "type": "string", "description": "Name of the ABAP program or include" }, "object_type": { "type": "string", "enum": [ "PROG/P", "PROG/I", "FUGR", "CLAS/OC" ], "description": "ADT object type (e.g. PROG/P, PROG/I, FUGR, CLAS/OC)" }, "detailed": { "type": "boolean", "description": "If true, returns structured JSON with metadata and raw XML.", "default": false }, "timeout": { "type": "number", "description": "Timeout in ms for each ADT request." } }, "required": [ "object_name", "object_type" ] } } as const; /** * Parses XML response to extract includes information * @param xmlData XML response data * @returns Array of include objects with name and node_id */ function parseIncludesFromXml(xmlData: string): Array<{name: string, node_id: string, label: string}> { const includes: Array<{name: string, node_id: string, label: string}> = []; try { // Simple regex-based parsing for XML // Look for OBJECT_TYPE entries that contain "PROG/I" (includes) const objectTypeRegex = /<SEU_ADT_OBJECT_TYPE_INFO>(.*?)<\/SEU_ADT_OBJECT_TYPE_INFO>/gs; const matches = xmlData.match(objectTypeRegex); if (matches) { for (const match of matches) { // Check if this is an include type if (match.includes('<OBJECT_TYPE>PROG/I</OBJECT_TYPE>')) { const nodeIdMatch = match.match(/<NODE_ID>(\d+)<\/NODE_ID>/); const labelMatch = match.match(/<OBJECT_TYPE_LABEL>(.*?)<\/OBJECT_TYPE_LABEL>/); if (nodeIdMatch && labelMatch) { includes.push({ name: 'PROG/I', node_id: nodeIdMatch[1], label: labelMatch[1] }); } } } } } catch (error) { // console.warn('Error parsing XML for includes:', error); } return includes; } /** * Parses XML response to extract actual include names from node structure * @param xmlData XML response data * @returns Array of include names */ function parseIncludeNamesFromXml(xmlData: string): string[] { const includeNames: string[] = []; try { // Look for SEU_ADT_REPOSITORY_OBJ_NODE entries with OBJECT_TYPE PROG/I const nodeRegex = /<SEU_ADT_REPOSITORY_OBJ_NODE>(.*?)<\/SEU_ADT_REPOSITORY_OBJ_NODE>/gs; const nodeMatches = xmlData.match(nodeRegex); if (nodeMatches) { for (const nodeMatch of nodeMatches) { // Check if this node is for includes (PROG/I) if (nodeMatch.includes('<OBJECT_TYPE>PROG/I</OBJECT_TYPE>')) { // Extract the object name const nameMatch = nodeMatch.match(/<OBJECT_NAME>([^<]+)<\/OBJECT_NAME>/); if (nameMatch && nameMatch[1].trim()) { const includeName = nameMatch[1].trim(); // Decode URL-encoded names if needed const decodedName = decodeURIComponent(includeName); includeNames.push(decodedName); } } } } // If no nodes found, try alternative parsing for OBJECT_NAME tags if (includeNames.length === 0) { const objectNameRegex = /<OBJECT_NAME>([^<]+)<\/OBJECT_NAME>/g; let match; while ((match = objectNameRegex.exec(xmlData)) !== null) { const name = match[1].trim(); if (name && name.length > 0) { const decodedName = decodeURIComponent(name); includeNames.push(decodedName); } } } } catch (error) { // console.warn('Error parsing XML for include names:', error); } return [...new Set(includeNames)]; // Remove duplicates } export async function handleGetIncludesList(args: any) { try { const { object_name, object_type, timeout, detailed } = args; if (!object_name || typeof object_name !== 'string' || object_name.trim() === '') { throw new McpError(ErrorCode.InvalidParams, 'Parameter "object_name" (string) is required and cannot be empty.'); } if (!object_type || typeof object_type !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'Parameter "object_type" (string) is required.'); } // Default timeout: 30 seconds const requestTimeout = timeout && typeof timeout === 'number' ? timeout : 30000; const isDetailed = detailed === true; // Pass object_type straight through as parentType let parentName = object_name; let parentTechName = object_name; let parentType = object_type; // Step 1: Get root node structure to find includes node (with timeout) const rootResponse = await Promise.race([ fetchNodeStructure( parentName.toUpperCase(), parentTechName.toUpperCase(), parentType, '000000', // Root node true // with descriptions ), new Promise<never>((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${requestTimeout}ms while fetching root node structure for ${object_name}`)), requestTimeout) ) ]); // Step 2: Parse response to find includes node ID const includesInfo = parseIncludesFromXml(rootResponse.data); const includesNode = includesInfo.find(info => info.name === 'PROG/I'); if (!includesNode) { // Return empty result if no includes node found return { isError: false, content: [ { type: "text", text: `No includes found in ${object_type} '${object_name}'.` } ] }; } // Step 3: Get includes list using the found node ID (with timeout) const includesResponse = await Promise.race([ fetchNodeStructure( parentName.toUpperCase(), parentTechName.toUpperCase(), parentType, includesNode.node_id, true // with descriptions ), new Promise<never>((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${requestTimeout}ms while fetching includes list for ${object_name}`)), requestTimeout) ) ]); // Step 4: Parse the includes response to extract include names const includeNames = parseIncludeNamesFromXml(includesResponse.data); if (isDetailed) { // Return detailed JSON response as text (for compatibility) const detailedResponse = { object_name: object_name, object_type: object_type, detailed: true, total_includes: includeNames.length, includes: includeNames, includes_node_info: includesNode }; const result = { isError: false, content: [ { type: "text", text: JSON.stringify(detailedResponse, null, 2) } ] }; if (args.filePath) { writeResultToFile(JSON.stringify(result, null, 2), args.filePath); } return result; } else { // Return minimal text response (original format) const responseData = includeNames.length > 0 ? includeNames.join('\n') : ''; const plainResult = { isError: false, content: [ { type: "text", text: responseData } ] }; if (args.filePath) { writeResultToFile(responseData, args.filePath); } return plainResult; } } catch (error) { return { isError: true, content: [ { type: "text", text: error instanceof Error ? error.message : 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