Skip to main content
Glama
marianfoo

SAP Documentation MCP Server

by marianfoo

fetch

Retrieve full SAP documentation content and metadata by providing a specific document ID from search results, returning structured JSON for developer reference.

Instructions

GET SPECIFIC DOCS: fetch(id="result_id")

FUNCTION NAME: fetch

RETRIEVES: Full content from search results WORKS WITH: Document IDs returned by search

ChatGPT COMPATIBLE: • Uses "id" parameter (required by ChatGPT) • Returns structured JSON content • Includes full document text and metadata

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesUnique document ID from search results. Use exact IDs returned by search.

Implementation Reference

  • MCP tool handler for 'fetch': extracts id parameter, calls fetchLibraryDocumentation, formats structured JSON response with document content.
    if (name === "sap_docs_get" || name === "fetch") {
      // Handle both old format (library_id) and new ChatGPT format (id)
      const library_id = (args as any).library_id || (args as any).id;
      const topic = (args as any).topic || "";
      
      if (!library_id) {
        const timing = logger.logToolStart(name, 'missing_id', clientMetadata);
        logger.logToolError(name, timing.requestId, timing.startTime, new Error('Missing id parameter'));
        return createErrorResponse(
          `Missing required parameter: id. Please provide a document ID from search results.`,
          timing.requestId
        );
      }
      
      // Enhanced logging with timing
      const searchKey = library_id + (topic ? `/${topic}` : '');
      const timing = logger.logToolStart(name, searchKey, clientMetadata);
      
      try {
        const text = await fetchLibraryDocumentation(library_id, topic);
        
        if (!text) {
          logger.logToolSuccess(name, timing.requestId, timing.startTime, 0);
          return createErrorResponse(
            `Nothing found for ${library_id}`,
            timing.requestId
          );
        }
        
        // Transform document content to ChatGPT-compatible format
        const config = getDocUrlConfig(library_id);
        const docUrl = config ? generateDocumentationUrl(library_id, '', text, config) : null;
        const document: DocumentResult = {
          id: library_id,
          title: library_id.replace(/^\//, '').replace(/\//g, ' > ') + (topic ? ` (${topic})` : ''),
          text: text,
          url: docUrl || `#${library_id}`,
          metadata: {
            source: 'sap-docs',
            library: library_id,
            topic: topic || undefined,
            contentLength: text.length
          }
        };
        
        logger.logToolSuccess(name, timing.requestId, timing.startTime, 1, { 
          contentLength: text.length,
          libraryId: library_id,
          topic: topic || undefined
        });
        
        return createDocumentResponse(document);
      } catch (error) {
        logger.logToolError(name, timing.requestId, timing.startTime, error);
        return createErrorResponse(
          `Error retrieving documentation for ${library_id}. Please try again later.`,
          timing.requestId
        );
      }
    }
  • Tool schema definition: name 'fetch', description, inputSchema requiring 'id' string parameter with examples.
                name: "fetch",
                description: `GET SPECIFIC DOCS: fetch(id="result_id")
    
    FUNCTION NAME: fetch
    
    RETRIEVES: Full content from search results
    WORKS WITH: Document IDs returned by search
    
    ChatGPT COMPATIBLE:
    • Uses "id" parameter (required by ChatGPT)
    • Returns structured JSON content
    • Includes full document text and metadata`,
                inputSchema: {
                  type: "object",
                  properties: {
                    id: {
                      type: "string",
                      description: "Unique document ID from search results. Use exact IDs returned by search.",
                      examples: [
                        "/cap/guides/domain-modeling",
                        "/sapui5/controls/button-properties", 
                        "/openui5-api/sap/m/Button",
                        "/abap-docs-758/inline-declarations",
                        "community-12345"
                      ]
                    }
                  },
                  required: ["id"]
                }
              },
  • Registers 'fetch' tool in ListToolsRequestSchema handler by including it in the tools array.
        srv.setRequestHandler(ListToolsRequestSchema, async () => {
          return {
            tools: [
              {
                name: "sap_community_search", 
                description: `SEARCH SAP COMMUNITY: sap_community_search(query="search terms")
    
    FUNCTION NAME: sap_community_search (or mcp_sap-docs-remote_sap_community_search)
    
    FINDS: Blog posts, discussions, solutions from SAP Community
    INCLUDES: Engagement data (kudos), ranked by "Best Match"
    
    TYPICAL WORKFLOW:
    1. sap_community_search(query="your problem + error code")
    2. fetch(id="community-12345") for full posts
    
    BEST FOR TROUBLESHOOTING:
    • Include error codes: "415 error", "500 error"
    • Be specific: "CAP action binary upload 415"
    • Use real scenarios: "wizard implementation issues"`,
                inputSchema: {
                  type: "object",
                  properties: {
                    query: {
                      type: "string",
                      description: "Search terms for SAP Community. Include error codes and specific technical details.",
                      examples: [
                        "CAP action parameter binary file upload 415 error",
                        "wizard implementation best practices",
                        "fiori elements authentication",
                        "UI5 deployment issues",
                        "wdi5 test automation problems"
                      ]
                    }
                  },
                  required: ["query"]
                }
              },
              {
                name: "sap_help_search",
                description: `SEARCH SAP HELP PORTAL: sap_help_search(query="product + topic")
    
    FUNCTION NAME: sap_help_search (or mcp_sap-docs-remote_sap_help_search)
    
    SEARCHES: Official SAP Help Portal (help.sap.com)
    COVERS: Product guides, implementation guides, technical documentation
    
    TYPICAL WORKFLOW:
    1. sap_help_search(query="product name + configuration topic")
    2. sap_help_get(result_id="sap-help-12345abc")
    
    BEST PRACTICES:
    • Include product names: "S/4HANA", "BTP", "Fiori"
    • Add specific tasks: "configuration", "setup", "deployment"
    • Use official SAP terminology`,
                inputSchema: {
                  type: "object",
                  properties: {
                    query: {
                      type: "string",
                      description: "Search terms for SAP Help Portal. Include product names and specific topics.",
                      examples: [
                        "S/4HANA configuration",
                        "Fiori Launchpad setup", 
                        "BTP integration",
                        "ABAP development guide",
                        "SAP Analytics Cloud setup"
                      ]
                    }
                  },
                  required: ["query"]
                }
              },
              {
                name: "sap_help_get", 
                description: `GET SAP HELP PAGE: sap_help_get(result_id="sap-help-12345abc")
    
    FUNCTION NAME: sap_help_get (or mcp_sap-docs-remote_sap_help_get)
    
    RETRIEVES: Complete SAP Help Portal page content
    REQUIRES: Exact result_id from sap_help_search
    
    USAGE PATTERN:
    1. Get ID from sap_help_search results  
    2. Use exact ID (don't modify the format)
    3. Receive full page content + metadata`,
                inputSchema: {
                  type: "object",
                  properties: {
                    result_id: {
                      type: "string",
                      description: "Exact ID from sap_help_search results. Copy the ID exactly as returned.",
                      examples: [
                        "sap-help-12345abc",
                        "sap-help-98765def"
                      ]
                    }
                  },
                  required: ["result_id"]
                }
              },
              {
                name: "search",
                description: `SEARCH SAP DOCS: search(query="search terms")
    
    FUNCTION NAME: search
    
    COVERS: ABAP (all versions), UI5, CAP, wdi5, OpenUI5 APIs, Cloud SDK
    AUTO-DETECTS: ABAP versions from query (e.g. "LOOP 7.57", defaults to 7.58)
    
    TYPICAL WORKFLOW:
    1. search(query="your search terms") 
    2. fetch(id="result_id_from_step_1")
    
    QUERY TIPS:
    • Be specific: "CAP action binary parameter" not just "CAP"
    • Include error codes: "415 error CAP action"
    • Use technical terms: "LargeBinary MediaType XMLHttpRequest"
    • For ABAP: Include version like "7.58" or "latest"`,
                inputSchema: {
                  type: "object",
                  properties: {
                    query: {
                      type: "string",
                      description: "Search terms using natural language. Be specific and include technical terms.",
                      examples: [
                        "CAP binary data LargeBinary MediaType",
                        "UI5 button properties",
                        "wdi5 testing locators", 
                        "ABAP SELECT statements 7.58",
                        "415 error CAP action parameter"
                      ]
                    }
                  },
                  required: ["query"]
                }
              },
              {
                name: "fetch",
                description: `GET SPECIFIC DOCS: fetch(id="result_id")
    
    FUNCTION NAME: fetch
    
    RETRIEVES: Full content from search results
    WORKS WITH: Document IDs returned by search
    
    ChatGPT COMPATIBLE:
    • Uses "id" parameter (required by ChatGPT)
    • Returns structured JSON content
    • Includes full document text and metadata`,
                inputSchema: {
                  type: "object",
                  properties: {
                    id: {
                      type: "string",
                      description: "Unique document ID from search results. Use exact IDs returned by search.",
                      examples: [
                        "/cap/guides/domain-modeling",
                        "/sapui5/controls/button-properties", 
                        "/openui5-api/sap/m/Button",
                        "/abap-docs-758/inline-declarations",
                        "community-12345"
                      ]
                    }
                  },
                  required: ["id"]
                }
              },
    
            ]
          };
        });
  • Core fetch implementation: loads doc index, resolves library/doc ID, reads source file, formats content specially for API docs/samples, handles community posts, returns markdown text.
    export async function fetchLibraryDocumentation(
      libraryIdOrDocId: string,
      topic = ""
    ): Promise<string | null> {
      // Check if this is a community post ID
      if (libraryIdOrDocId.startsWith('community-')) {
        return await getCommunityPost(libraryIdOrDocId);
      }
    
      const index = await loadIndex();
      
      // Check if this is a specific document ID
      const allDocs: Array<{lib: any, doc: any}> = [];
      for (const lib of Object.values(index)) {
        for (const doc of lib.docs) {
          allDocs.push({ lib, doc });
          // Try exact match first (for section documents with fragments)
          if (doc.id === libraryIdOrDocId) {
            const sourcePath = getSourcePath(lib.id);
            if (!sourcePath) {
              throw new Error(`Unknown library ID: ${lib.id}`);
            }
            
            const abs = path.join(PROJECT_ROOT, "sources", sourcePath, doc.relFile);
            const content = await fs.readFile(abs, "utf8");
            
            // For JavaScript API files, format the content for better readability
            if (doc.relFile && doc.relFile.endsWith('.js') && lib.id === '/openui5-api') {
              return formatJSDocContent(content, doc.title || '');
            }
            // For sample files, format them appropriately
            else if (lib.id === '/openui5-samples') {
              return formatSampleContent(content, doc.relFile, doc.title || '');
            }
            // For documented libraries, add URL context
            else if (getDocUrlConfig(lib.id)) {
              const documentationUrl = generateDocumentationUrl(lib.id, doc.relFile, content);
              const libName = lib.id.replace('/', '').toUpperCase();
              
              return `**Source:** ${libName} Documentation
    **URL:** ${documentationUrl || 'Documentation URL not available'}
    **File:** ${doc.relFile}
    
    ---
    
    ${content}
    
    ---
    
    *This content is from the ${libName} documentation. Visit the URL above for the latest version and interactive examples.*`;
            } else {
              return content;
            }
          }
        }
      }
      
      // If no exact match found and the ID contains a fragment, try stripping the fragment
      // This handles cases where fragments are used for navigation but don't exist as separate documents
      if (libraryIdOrDocId.includes('#')) {
        const baseId = libraryIdOrDocId.split('#')[0];
        
        // Try to find a document with the base ID (without fragment)
        for (const lib of Object.values(index)) {
          for (const doc of lib.docs) {
            if (doc.id === baseId) {
              const sourcePath = getSourcePath(lib.id);
              if (!sourcePath) {
                throw new Error(`Unknown library ID: ${lib.id}`);
              }
              
              const abs = path.join(PROJECT_ROOT, "sources", sourcePath, doc.relFile);
              const content = await fs.readFile(abs, "utf8");
              
              // For JavaScript API files, format the content for better readability
              if (doc.relFile && doc.relFile.endsWith('.js') && lib.id === '/openui5-api') {
                return formatJSDocContent(content, doc.title || '');
              }
              // For sample files, format them appropriately
              else if (lib.id === '/openui5-samples') {
                return formatSampleContent(content, doc.relFile, doc.title || '');
              }
              // For documented libraries, add URL context
              else if (getDocUrlConfig(lib.id)) {
                const documentationUrl = generateDocumentationUrl(lib.id, doc.relFile, content);
                const libName = lib.id.replace('/', '').toUpperCase();
                
                return `**Source:** ${libName} Documentation
    **URL:** ${documentationUrl || 'Documentation URL not available'}
    **File:** ${doc.relFile}
    
    ---
    
    ${content}
    
    ---
    
    *This content is from the ${libName} documentation. Visit the URL above for the latest version and interactive examples.*`;
              } else {
                return content;
              }
            }
          }
        }
        
        // Try library-level lookup with base ID
        const baseLib = index[baseId];
        if (baseLib) {
          const term = topic.toLowerCase();
          const targets = term
            ? baseLib.docs.filter(
                (d) =>
                  d.title.toLowerCase().includes(term) ||
                  d.description.toLowerCase().includes(term)
              )
            : baseLib.docs;
    
          if (!targets.length) return `No topic "${topic}" found inside ${baseId}.`;
    
          const parts: string[] = [];
          for (const doc of targets) {
            const sourcePath = getSourcePath(baseLib.id);
            if (!sourcePath) {
              throw new Error(`Unknown library ID: ${baseLib.id}`);
            }
            
            const abs = path.join(PROJECT_ROOT, "sources", sourcePath, doc.relFile);
            const content = await fs.readFile(abs, "utf8");
            
            // For JavaScript API files, format the content for better readability
            if (doc.relFile && doc.relFile.endsWith('.js') && baseLib.id === '/openui5-api') {
              const formattedContent = formatJSDocContent(content, doc.title || '');
              parts.push(formattedContent);
            }
            // For sample files, format them appropriately
            else if (baseLib.id === '/openui5-samples') {
              const formattedContent = formatSampleContent(content, doc.relFile, doc.title || '');
              parts.push(formattedContent);
            }
            // For documented libraries, add URL context
            else if (getDocUrlConfig(baseLib.id)) {
              const documentationUrl = generateDocumentationUrl(baseLib.id, doc.relFile, content);
              const libName = baseLib.id.replace('/', '').toUpperCase();
              
              const formattedContent = `**Source:** ${libName} Documentation
    **URL:** ${documentationUrl || 'Documentation URL not available'}
    **File:** ${doc.relFile}
    
    ---
    
    ${content}
    
    ---
    
    *This content is from the ${libName} documentation. Visit the URL above for the latest version and interactive examples.*`;
              parts.push(formattedContent);
            } else {
              parts.push(content);
            }
          }
          return parts.join("\n\n---\n\n");
        }
      }
      
      // If not a specific document ID, treat as library ID with optional topic
      const lib = index[libraryIdOrDocId];
      if (!lib) return null;
    
      // If topic is provided, first try to construct the full document ID
      if (topic) {
        const fullDocId = `${libraryIdOrDocId}/${topic}`;
        
        // Try to find exact document match first
        for (const doc of lib.docs) {
          if (doc.id === fullDocId) {
            const sourcePath = getSourcePath(lib.id);
            if (!sourcePath) {
              throw new Error(`Unknown library ID: ${lib.id}`);
            }
            
            const abs = path.join(PROJECT_ROOT, "sources", sourcePath, doc.relFile);
            const content = await fs.readFile(abs, "utf8");
            
            // Format the content appropriately based on library type
            if (doc.relFile && doc.relFile.endsWith('.js') && lib.id === '/openui5-api') {
              return formatJSDocContent(content, doc.title || '');
            } else if (lib.id === '/openui5-samples') {
              return formatSampleContent(content, doc.relFile, doc.title || '');
            } else if (getDocUrlConfig(lib.id)) {
              const documentationUrl = generateDocumentationUrl(lib.id, doc.relFile, content);
              const libName = lib.id.replace('/', '').toUpperCase();
              
              return `**Source:** ${libName} Documentation
    **URL:** ${documentationUrl || 'Documentation URL not available'}
    **File:** ${doc.relFile}
    
    ---
    
    ${content}
    
    ---
    
    *This content is from the ${libName} documentation. Visit the URL above for the latest version and interactive examples.*`;
            } else {
              return content;
            }
          }
        }
        
        // If exact match not found, fall back to topic keyword search
        const term = topic.toLowerCase();
        const targets = lib.docs.filter(
          (d) =>
            d.title.toLowerCase().includes(term) ||
            d.description.toLowerCase().includes(term)
        );
        
        if (targets.length > 0) {
          // Process the filtered documents
          const parts: string[] = [];
          for (const doc of targets) {
            const sourcePath = getSourcePath(lib.id);
            if (!sourcePath) {
              throw new Error(`Unknown library ID: ${lib.id}`);
            }
            
            const abs = path.join(PROJECT_ROOT, "sources", sourcePath, doc.relFile);
            const content = await fs.readFile(abs, "utf8");
            
            if (doc.relFile && doc.relFile.endsWith('.js') && lib.id === '/openui5-api') {
              const formattedContent = formatJSDocContent(content, doc.title || '');
              parts.push(formattedContent);
            } else if (lib.id === '/openui5-samples') {
              const formattedContent = formatSampleContent(content, doc.relFile, doc.title || '');
              parts.push(formattedContent);
            } else if (getDocUrlConfig(lib.id)) {
              const documentationUrl = generateDocumentationUrl(lib.id, doc.relFile, content);
              const libName = lib.id.replace('/', '').toUpperCase();
              
              const formattedContent = `**Source:** ${libName} Documentation
    **URL:** ${documentationUrl || 'Documentation URL not available'}
    **File:** ${doc.relFile}
    
    ---
    
    ${content}
    
    ---
    
    *This content is from the ${libName} documentation. Visit the URL above for the latest version and interactive examples.*`;
              parts.push(formattedContent);
            } else {
              parts.push(content);
            }
          }
          return parts.join("\n\n---\n\n");
        }
        
        return `No topic "${topic}" found inside ${libraryIdOrDocId}.`;
      }
      
      // No topic provided, return all library documents
      const targets = lib.docs;
      if (!targets.length) return `No documents found inside ${libraryIdOrDocId}.`;
    
      const parts: string[] = [];
      for (const doc of targets) {
        const sourcePath = getSourcePath(lib.id);
        if (!sourcePath) {
          throw new Error(`Unknown library ID: ${lib.id}`);
        }
        
        const abs = path.join(PROJECT_ROOT, "sources", sourcePath, doc.relFile);
        const content = await fs.readFile(abs, "utf8");
        
        // For JavaScript API files, format the content for better readability
        if (doc.relFile && doc.relFile.endsWith('.js') && lib.id === '/openui5-api') {
          const formattedContent = formatJSDocContent(content, doc.title || '');
          parts.push(formattedContent);
        }
        // For sample files, format them appropriately
        else if (lib.id === '/openui5-samples') {
          const formattedContent = formatSampleContent(content, doc.relFile, doc.title || '');
          parts.push(formattedContent);
        }
        // For documented libraries, add URL context
        else if (getDocUrlConfig(lib.id)) {
          const documentationUrl = generateDocumentationUrl(lib.id, doc.relFile, content);
          const libName = lib.id.replace('/', '').toUpperCase();
          
          const formattedContent = `**Source:** ${libName} Documentation
    **URL:** ${documentationUrl || 'Documentation URL not available'}
    **File:** ${doc.relFile}
    
    ---
    
    ${content}
    
    ---
    
    *This content is from the ${libName} documentation. Visit the URL above for the latest version and interactive examples.*`;
          parts.push(formattedContent);
        } else {
          parts.push(content);
        }
      }
      return parts.join("\n\n---\n\n");
    }
  • Helper to format OpenUI5 API JavaScript files: extracts JSDoc, metadata, properties/events/aggregations into structured markdown.
    // Format JavaScript content for better readability in documentation context
    function formatJSDocContent(content: string, controlName: string): string {
      const lines = content.split('\n');
      const result: string[] = [];
      
      result.push(`# ${controlName} - OpenUI5 Control API`);
      result.push('');
      
      // Extract main JSDoc comment
      const mainJSDocMatch = content.match(/\/\*\*\s*([\s\S]*?)\*\//);
      if (mainJSDocMatch) {
        const cleanDoc = mainJSDocMatch[1]
          .split('\n')
          .map(line => line.replace(/^\s*\*\s?/, ''))
          .join('\n')
          .trim();
        
        result.push('## Description');
        result.push('');
        result.push(cleanDoc);
        result.push('');
      }
      
      // Extract metadata section
      const metadataMatch = content.match(/metadata\s*:\s*\{([\s\S]*?)\n\s*\}/);
      if (metadataMatch) {
        result.push('## Control Metadata');
        result.push('');
        result.push('```javascript');
        result.push('metadata: {');
        result.push(metadataMatch[1]);
        result.push('}');
        result.push('```');
        result.push('');
      }
      
      // Extract properties
      const propertiesMatch = content.match(/properties\s*:\s*\{([\s\S]*?)\n\s*\}/);
      if (propertiesMatch) {
        result.push('## Properties');
        result.push('');
        result.push('```javascript');
        result.push(propertiesMatch[1]);
        result.push('```');
        result.push('');
      }
      
      // Extract events
      const eventsMatch = content.match(/events\s*:\s*\{([\s\S]*?)\n\s*\}/);
      if (eventsMatch) {
        result.push('## Events');
        result.push('');
        result.push('```javascript');
        result.push(eventsMatch[1]);
        result.push('```');
        result.push('');
      }
      
      // Extract aggregations
      const aggregationsMatch = content.match(/aggregations\s*:\s*\{([\s\S]*?)\n\s*\}/);
      if (aggregationsMatch) {
        result.push('## Aggregations');
        result.push('');
        result.push('```javascript');
        result.push(aggregationsMatch[1]);
        result.push('```');
        result.push('');
      }
      
      // Extract associations
      const associationsMatch = content.match(/associations\s*:\s*\{([\s\S]*?)\n\s*\}/);
      if (associationsMatch) {
        result.push('## Associations');
        result.push('');
        result.push('```javascript');
        result.push(associationsMatch[1]);
        result.push('```');
        result.push('');
      }
      
      result.push('---');
      result.push('');
      result.push('### Full Source Code');
      result.push('');
      result.push('```javascript');
      result.push(content);
      result.push('```');
      
      return result.join('\n');
    }

Latest Blog Posts

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/marianfoo/mcp-sap-docs'

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