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
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | Unique document ID from search results. Use exact IDs returned by search. |
Implementation Reference
- src/lib/BaseServerHandler.ts:544-603 (handler)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 ); } }
- src/lib/BaseServerHandler.ts:360-389 (schema)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"] } },
- src/lib/BaseServerHandler.ts:222-393 (registration)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"] } }, ] }; });
- src/lib/localDocs.ts:1392-1699 (handler)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"); }
- src/lib/localDocs.ts:157-246 (helper)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'); }