summaryrefslogtreecommitdiff
path: root/packages/cli/src/config/extension.ts
diff options
context:
space:
mode:
authorTommaso Sciortino <[email protected]>2025-06-10 15:48:39 -0700
committerGitHub <[email protected]>2025-06-10 15:48:39 -0700
commit4e84431df3e5737a0687af59853504a4e5b9ae51 (patch)
treeb376b8ea3dbb3835b12b559f0c6437f293e27c8f /packages/cli/src/config/extension.ts
parent916cfee08d8ae1529eba9ee96e6db1ecff4c9388 (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.ts87
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;
+}