@@ -46,6 +46,8 @@ import { search } from "./search.js";
4646import { CONFIG } from "./config.js" ;
4747import { loadMetadata , getDocUrlConfig } from "./metadata.js" ;
4848import { generateDocumentationUrl , formatSearchResult } from "./url-generation/index.js" ;
49+ import { extractLibraryIdFromPath } from "./url-generation/utils.js" ;
50+ import { extractSourceUrlFromText , readSourceContentSync } from "./sourceContent.js" ;
4951import { isToolEnabled , getVariantName } from "./variant.js" ;
5052import { searchDiscoveryCenter , getDiscoveryCenterServiceDetails } from "./discoveryCenter/index.js" ;
5153
@@ -75,7 +77,7 @@ interface DocumentResult {
7577/**
7678 * Create structured JSON response for search results (ChatGPT-compatible)
7779 */
78- function createSearchResponse ( results : SearchResult [ ] ) : any {
80+ function createSearchResponse ( results : SearchResult [ ] , extra : Record < string , any > = { } ) : any {
7981 // Clean the results to avoid JSON serialization issues in MCP protocol
8082 const cleanedResults = results . map ( result => ( {
8183 // ChatGPT requires: id, title, url (other fields optional)
@@ -90,18 +92,44 @@ function createSearchResponse(results: SearchResult[]): any {
9092 metadata : result . metadata
9193 } ) ) ;
9294
95+ const payload = { results : cleanedResults , ...extra } ;
96+
9397 // ChatGPT expects: { "results": [...] } in JSON-encoded text content
9498 return {
9599 content : [
96100 {
97101 type : "text" ,
98- text : JSON . stringify ( { results : cleanedResults } )
102+ text : JSON . stringify ( payload )
99103 }
100104 ] ,
101- structuredContent : { results : cleanedResults }
105+ structuredContent : payload
102106 } ;
103107}
104108
109+ function createEmptySearchResponse ( message : string , requestId ?: string , extra : Record < string , any > = { } ) : any {
110+ return createSearchResponse ( [ ] , {
111+ error : message ,
112+ requestId : requestId || 'unknown' ,
113+ ...extra
114+ } ) ;
115+ }
116+
117+ function isAbsoluteHttpUrl ( url ?: string ) : boolean {
118+ return ! ! url && / ^ h t t p s ? : \/ \/ / i. test ( url ) ;
119+ }
120+
121+ function chooseSearchResultUrl ( docUrl : string | null , path : string | undefined , id : string ) : string {
122+ if ( isAbsoluteHttpUrl ( docUrl || undefined ) ) {
123+ return docUrl ! ;
124+ }
125+
126+ if ( isAbsoluteHttpUrl ( path ) ) {
127+ return path ! ;
128+ }
129+
130+ return `#${ id } ` ;
131+ }
132+
105133/**
106134 * Create structured JSON response for document fetch (ChatGPT-compatible)
107135 */
@@ -1017,7 +1045,7 @@ RETURNS (JSON):
10171045 if ( topResults . length === 0 ) {
10181046 console . log ( `⚠️ [SEARCH TOOL] No results found for query: "${ query } "` ) ;
10191047 logger . logToolSuccess ( name , timing . requestId , timing . startTime , 0 , { fallback : false } ) ;
1020- return createErrorResponse (
1048+ return createEmptySearchResponse (
10211049 `No results for "${ query } ". Try ABAP keywords ("SELECT", "LOOP", "RAP"), add "cloud" for ABAP Cloud syntax, or be more specific.` ,
10221050 timing . requestId
10231051 ) ;
@@ -1031,13 +1059,14 @@ RETURNS (JSON):
10311059 const topic = r . id . startsWith ( libraryId ) ? r . id . slice ( libraryId . length + 1 ) : '' ;
10321060
10331061 const config = getDocUrlConfig ( libraryId ) ;
1034- const docUrl = config ? generateDocumentationUrl ( libraryId , r . relFile || '' , r . text , config ) : null ;
1062+ const sourceContent = config ? readSourceContentSync ( libraryId , r . relFile || '' ) : null ;
1063+ const docUrl = config ? generateDocumentationUrl ( libraryId , r . relFile || '' , sourceContent || r . text , config ) : null ;
10351064
10361065 return {
10371066 // ChatGPT-required format: id, title, url
10381067 id : r . id ,
10391068 title : r . text . split ( '\n' ) [ 0 ] || r . id ,
1040- url : docUrl || r . path || `# ${ r . id } ` ,
1069+ url : chooseSearchResultUrl ( docUrl , r . path , r . id ) ,
10411070 // Additional fields
10421071 library_id : libraryId ,
10431072 topic : topic ,
@@ -1074,7 +1103,7 @@ RETURNS (JSON):
10741103
10751104 if ( ! res . results . length ) {
10761105 logger . logToolSuccess ( name , timing . requestId , timing . startTime , 0 , { fallback : true } ) ;
1077- return createErrorResponse (
1106+ return createEmptySearchResponse (
10781107 res . error || `No fallback results for "${ query } ". Try ABAP keywords ("SELECT", "LOOP", "RAP"), add "cloud" for ABAP Cloud syntax, or be more specific.` ,
10791108 timing . requestId
10801109 ) ;
@@ -1097,7 +1126,7 @@ RETURNS (JSON):
10971126 return createSearchResponse ( fallbackResults ) ;
10981127 } catch ( fallbackError ) {
10991128 logger . logToolError ( name , timing . requestId , timing . startTime , fallbackError , true ) ;
1100- return createErrorResponse (
1129+ return createEmptySearchResponse (
11011130 `Search temporarily unavailable. Wait 30 seconds and retry, or use more specific search terms.` ,
11021131 timing . requestId
11031132 ) ;
@@ -1135,13 +1164,15 @@ RETURNS (JSON):
11351164 }
11361165
11371166 // Transform document content to ChatGPT-compatible format
1138- const config = getDocUrlConfig ( library_id ) ;
1139- const docUrl = config ? generateDocumentationUrl ( library_id , '' , text , config ) : null ;
1167+ const fetchedSourceUrl = extractSourceUrlFromText ( text ) ;
1168+ const rootLibraryId = library_id . startsWith ( '/' ) ? extractLibraryIdFromPath ( library_id ) : library_id ;
1169+ const config = getDocUrlConfig ( rootLibraryId ) ;
1170+ const docUrl = config ? generateDocumentationUrl ( rootLibraryId , '' , text , config ) : null ;
11401171 const document : DocumentResult = {
11411172 id : library_id ,
11421173 title : library_id . replace ( / ^ \/ / , '' ) . replace ( / \/ / g, ' > ' ) + ( topic ? ` (${ topic } )` : '' ) ,
11431174 text : text ,
1144- url : docUrl || `#${ library_id } ` ,
1175+ url : fetchedSourceUrl || docUrl || `#${ library_id } ` ,
11451176 metadata : {
11461177 source : 'abap-docs' ,
11471178 library : library_id ,
@@ -1243,23 +1274,11 @@ RETURNS (JSON):
12431274
12441275 if ( ! communityResponse . results . length ) {
12451276 logger . logToolSuccess ( name , timing . requestId , timing . startTime , 0 ) ;
1246- return {
1247- content : [
1248- {
1249- type : "text" ,
1250- text : JSON . stringify ( {
1251- error : communityResponse . error || `No SAP Community posts found for "${ query } ". Try different keywords.` ,
1252- requestId : timing . requestId ,
1253- requestUrl,
1254- } ) ,
1255- } ,
1256- ] ,
1257- structuredContent : {
1258- error : communityResponse . error || `No SAP Community posts found for "${ query } ". Try different keywords.` ,
1259- requestId : timing . requestId ,
1260- requestUrl,
1261- } ,
1262- } ;
1277+ return createEmptySearchResponse (
1278+ communityResponse . error || `No SAP Community posts found for "${ query } ". Try different keywords.` ,
1279+ timing . requestId ,
1280+ { requestUrl }
1281+ ) ;
12631282 }
12641283
12651284 const searchResults : SearchResult [ ] = communityResponse . results . map ( ( r , index ) => ( {
@@ -1286,23 +1305,11 @@ RETURNS (JSON):
12861305 } ;
12871306 } catch ( error ) {
12881307 logger . logToolError ( name , timing . requestId , timing . startTime , error ) ;
1289- return {
1290- content : [
1291- {
1292- type : "text" ,
1293- text : JSON . stringify ( {
1294- error : "Error searching SAP Community. Please try again later." ,
1295- requestId : timing . requestId ,
1296- requestUrl,
1297- } ) ,
1298- } ,
1299- ] ,
1300- structuredContent : {
1301- error : "Error searching SAP Community. Please try again later." ,
1302- requestId : timing . requestId ,
1303- requestUrl,
1304- } ,
1305- } ;
1308+ return createEmptySearchResponse (
1309+ "Error searching SAP Community. Please try again later." ,
1310+ timing . requestId ,
1311+ { requestUrl }
1312+ ) ;
13061313 }
13071314 }
13081315
0 commit comments