mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-27 05:24:34 -07:00
feat: Add support for MCP Resources (#13178)
Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import type { Config, FileSearch } from '@google/gemini-cli-core';
|
||||
import { FileSearchFactory, escapePath } from '@google/gemini-cli-core';
|
||||
import type { Suggestion } from '../components/SuggestionsDisplay.js';
|
||||
import { MAX_SUGGESTIONS_TO_SHOW } from '../components/SuggestionsDisplay.js';
|
||||
import { AsyncFzf } from 'fzf';
|
||||
|
||||
export enum AtCompletionStatus {
|
||||
IDLE = 'idle',
|
||||
@@ -97,6 +98,61 @@ export interface UseAtCompletionProps {
|
||||
setIsLoadingSuggestions: (isLoading: boolean) => void;
|
||||
}
|
||||
|
||||
interface ResourceSuggestionCandidate {
|
||||
searchKey: string;
|
||||
suggestion: Suggestion;
|
||||
}
|
||||
|
||||
function buildResourceCandidates(
|
||||
config?: Config,
|
||||
): ResourceSuggestionCandidate[] {
|
||||
const registry = config?.getResourceRegistry?.();
|
||||
if (!registry) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const resources = registry.getAllResources().map((resource) => {
|
||||
// Use serverName:uri format to disambiguate resources from different MCP servers
|
||||
const prefixedUri = `${resource.serverName}:${resource.uri}`;
|
||||
return {
|
||||
// Include prefixedUri in searchKey so users can search by the displayed format
|
||||
searchKey: `${prefixedUri} ${resource.name ?? ''}`.toLowerCase(),
|
||||
suggestion: {
|
||||
label: prefixedUri,
|
||||
value: prefixedUri,
|
||||
},
|
||||
} satisfies ResourceSuggestionCandidate;
|
||||
});
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
async function searchResourceCandidates(
|
||||
pattern: string,
|
||||
candidates: ResourceSuggestionCandidate[],
|
||||
): Promise<Suggestion[]> {
|
||||
if (candidates.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const normalizedPattern = pattern.toLowerCase();
|
||||
if (!normalizedPattern) {
|
||||
return candidates
|
||||
.slice(0, MAX_SUGGESTIONS_TO_SHOW)
|
||||
.map((candidate) => candidate.suggestion);
|
||||
}
|
||||
|
||||
const fzf = new AsyncFzf(candidates, {
|
||||
selector: (candidate: ResourceSuggestionCandidate) => candidate.searchKey,
|
||||
});
|
||||
const results = await fzf.find(normalizedPattern, {
|
||||
limit: MAX_SUGGESTIONS_TO_SHOW * 3,
|
||||
});
|
||||
return results.map(
|
||||
(result: { item: ResourceSuggestionCandidate }) => result.item.suggestion,
|
||||
);
|
||||
}
|
||||
|
||||
export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
const {
|
||||
enabled,
|
||||
@@ -210,11 +266,28 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const suggestions = results.map((p) => ({
|
||||
const fileSuggestions = results.map((p) => ({
|
||||
label: p,
|
||||
value: escapePath(p),
|
||||
}));
|
||||
dispatch({ type: 'SEARCH_SUCCESS', payload: suggestions });
|
||||
|
||||
const resourceCandidates = buildResourceCandidates(config);
|
||||
const resourceSuggestions = (
|
||||
await searchResourceCandidates(
|
||||
state.pattern ?? '',
|
||||
resourceCandidates,
|
||||
)
|
||||
).map((suggestion) => ({
|
||||
...suggestion,
|
||||
label: suggestion.label.replace(/^@/, ''),
|
||||
value: suggestion.value.replace(/^@/, ''),
|
||||
}));
|
||||
|
||||
const combinedSuggestions = [
|
||||
...fileSuggestions,
|
||||
...resourceSuggestions,
|
||||
];
|
||||
dispatch({ type: 'SEARCH_SUCCESS', payload: combinedSuggestions });
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error && error.name === 'AbortError')) {
|
||||
dispatch({ type: 'ERROR' });
|
||||
|
||||
Reference in New Issue
Block a user