Skip to content

Commit 9bd831d

Browse files
committed
improve search query
1 parent 81e7010 commit 9bd831d

File tree

2 files changed

+90
-15
lines changed

2 files changed

+90
-15
lines changed

src/asset.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,8 @@ export class Asset implements Asset {
136136
text: `The request was not authenticated. ${unAuthMsg || ''}`,
137137
});
138138
}
139-
logger.info(
140-
`API call '${url}' completed successfully with response: ${JSON.stringify(result)}`
141-
);
139+
logger.info(`API call '${url}' completed successfully.`);
140+
logger.debug(`Response: ${JSON.stringify(result)}`);
142141
return result;
143142
} catch (error) {
144143
logger.error(`Error calling API '${url}': ${error}`);

src/search.ts

Lines changed: 88 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ export class Search extends Asset {
9292
this.server.registerTool(
9393
'search',
9494
{
95-
description: 'Search for repositories',
95+
description:
96+
'Search for repositories. It sorts results by best match if no sort criteria is provided.',
9697
inputSchema: {
9798
query: z.string().describe('The query to search for'),
9899
badges: z
@@ -129,12 +130,14 @@ export class Search extends Asset {
129130
sort: z
130131
.enum(['pull_count', 'updated_at'])
131132
.optional()
133+
.nullable()
132134
.describe(
133-
'The criteria to sort the search results by. If the `sort` field is not set, pull count is used by default. When search extensions, documents are sort alphabetically if none is provided.'
135+
'The criteria to sort the search results by. If the `sort` field is not set, the best match is used by default. When search extensions, documents are sort alphabetically if none is provided. Do not use it unless user explicitly asks for it.'
134136
),
135137
order: z
136138
.enum(['asc', 'desc'])
137139
.optional()
140+
.nullable()
138141
.describe('The order to sort the search results by'),
139142
images: z
140143
.array(z.string())
@@ -161,11 +164,11 @@ export class Search extends Asset {
161164
extension_reviewed?: boolean;
162165
from?: number;
163166
size?: number;
164-
sort?: 'pull_count' | 'updated_at';
165-
order?: 'asc' | 'desc';
167+
sort?: 'pull_count' | 'updated_at' | null;
168+
order?: 'asc' | 'desc' | null;
166169
images?: string[];
167170
}): Promise<CallToolResult> {
168-
logger.info(`Searching for repositories with query: ${request.query}`);
171+
logger.info(`Searching for repositories with request: ${JSON.stringify(request)}`);
169172
let url = `${this.config.host}/v4?custom_boosted_results=true`;
170173
if (!request.query) {
171174
return {
@@ -176,19 +179,92 @@ export class Search extends Asset {
176179
}
177180
const queryParams = new URLSearchParams();
178181
for (const key in request) {
179-
if (
180-
request[key as keyof typeof request] !== undefined &&
181-
request[key as keyof typeof request] !== null
182-
) {
183-
queryParams.set(key, request[key as keyof typeof request] as string);
182+
const param = key as keyof typeof request;
183+
switch (param) {
184+
case 'badges':
185+
case 'categories':
186+
case 'architectures':
187+
case 'operating_systems':
188+
case 'images': {
189+
if (request[param] && request[param].length > 0) {
190+
queryParams.set(param, request[param].join(','));
191+
}
192+
break;
193+
}
194+
case 'query':
195+
case 'type':
196+
case 'order':
197+
case 'sort':
198+
case 'from':
199+
case 'size': {
200+
if (
201+
request[param] !== undefined &&
202+
request[param] !== null &&
203+
request[param] !== ''
204+
) {
205+
queryParams.set(param, request[param].toString());
206+
logger.info(`Setting parameter: ${param} to ${request[param]}`);
207+
}
208+
break;
209+
}
210+
case 'extension_reviewed': {
211+
if (request[param]) {
212+
queryParams.set(param, 'true');
213+
}
214+
break;
215+
}
216+
case 'from':
217+
case 'size': {
218+
if (request[param] !== undefined && request[param] !== null) {
219+
queryParams.set(param, request[param].toString());
220+
}
221+
break;
222+
}
223+
default: {
224+
logger.warn(`Unknown parameter: ${param}`);
225+
break;
226+
}
184227
}
185228
}
186-
url += `?${queryParams.toString()}`;
187-
return this.callAPI<typeof searchResults>(
229+
if (queryParams.size > 0) {
230+
url += `&${queryParams.toString()}`;
231+
}
232+
const response = await this.callAPI<typeof searchResults>(
188233
url,
189234
{ method: 'GET' },
190235
`Here are the search results: :response`,
191236
`Error finding repositories for query: ${request.query}`
192237
);
238+
// let's try to find if the query is an exact match
239+
if (!response.isError) {
240+
if (response.structuredContent) {
241+
try {
242+
const results = searchResults.parse(response.structuredContent).results;
243+
if (results && results.length > 0) {
244+
const [namespace, repository] = results[0].name.split('/');
245+
if (
246+
!namespace.toLowerCase().includes(request.query.toLowerCase()) ||
247+
!repository.toLowerCase().includes(request.query.toLowerCase())
248+
) {
249+
return {
250+
content: [
251+
{
252+
type: 'text',
253+
text: `We could not find any repository exactly matching '${request.query}'. However we found some repositories that might be relevant.`,
254+
},
255+
...response.content,
256+
],
257+
structuredContent: response.structuredContent,
258+
};
259+
}
260+
}
261+
} catch (error) {
262+
logger.error(`Error parsing search results: ${error}`);
263+
// return the original response if we can't parse the results
264+
return response;
265+
}
266+
}
267+
}
268+
return response;
193269
}
194270
}

0 commit comments

Comments
 (0)