diff options
| author | Tommaso Sciortino <[email protected]> | 2025-06-10 15:48:39 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-10 15:48:39 -0700 |
| commit | 4e84431df3e5737a0687af59853504a4e5b9ae51 (patch) | |
| tree | b376b8ea3dbb3835b12b559f0c6437f293e27c8f /packages/cli/src/config/extension.ts | |
| parent | 916cfee08d8ae1529eba9ee96e6db1ecff4c9388 (diff) | |
Allow simple extensions for registering MCPservers (#890)
Diffstat (limited to 'packages/cli/src/config/extension.ts')
| -rw-r--r-- | packages/cli/src/config/extension.ts | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/packages/cli/src/config/extension.ts b/packages/cli/src/config/extension.ts new file mode 100644 index 00000000..641cfcb5 --- /dev/null +++ b/packages/cli/src/config/extension.ts @@ -0,0 +1,87 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MCPServerConfig } from '@gemini-cli/core'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; + +export const EXTENSIONS_DIRECTORY_NAME = path.join('.gemini', 'extensions'); +export const EXTENSIONS_CONFIG_FILENAME = 'gemini-extension.json'; + +export interface ExtensionConfig { + name: string; + version: string; + mcpServers?: Record<string, MCPServerConfig>; + contextFileName?: string; +} + +export function loadExtensions(workspaceDir: string): ExtensionConfig[] { + const allExtensions = [ + ...loadExtensionsFromDir(workspaceDir), + ...loadExtensionsFromDir(os.homedir()), + ]; + + const uniqueExtensions: ExtensionConfig[] = []; + const seenNames = new Set<string>(); + for (const extension of allExtensions) { + if (!seenNames.has(extension.name)) { + console.log( + `Loading extension: ${extension.name} (version: ${extension.version})`, + ); + uniqueExtensions.push(extension); + seenNames.add(extension.name); + } + } + + return uniqueExtensions; +} + +function loadExtensionsFromDir(dir: string): ExtensionConfig[] { + const extensionsDir = path.join(dir, EXTENSIONS_DIRECTORY_NAME); + if (!fs.existsSync(extensionsDir)) { + return []; + } + + const extensions: ExtensionConfig[] = []; + for (const subdir of fs.readdirSync(extensionsDir)) { + const extensionDir = path.join(extensionsDir, subdir); + + if (!fs.statSync(extensionDir).isDirectory()) { + console.error( + `Warning: unexpected file ${extensionDir} in extensions directory.`, + ); + continue; + } + + const extensionPath = path.join(extensionDir, EXTENSIONS_CONFIG_FILENAME); + if (!fs.existsSync(extensionPath)) { + console.error( + `Warning: extension directory ${extensionDir} does not contain a config file ${extensionPath}.`, + ); + continue; + } + + try { + const fileContent = fs.readFileSync(extensionPath, 'utf-8'); + const extensionConfig = JSON.parse(fileContent) as ExtensionConfig; + if (!extensionConfig.name || !extensionConfig.version) { + console.error( + `Invalid extension config in ${extensionPath}: missing name or version.`, + ); + continue; + } + extensions.push(extensionConfig); + } catch (e) { + console.error( + `Failed to load extension config from ${extensionPath}:`, + e, + ); + } + } + + return extensions; +} |
