From f6c8376ad8a2d766780120459ea77ae0fc36af8e Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Wed, 17 Sep 2025 14:03:49 -0400 Subject: [PATCH 1/3] fix: remove gemini extension installation --- .../commands/init-ai-tools/ai-tools/gemini.ts | 166 +++++++----------- 1 file changed, 66 insertions(+), 100 deletions(-) diff --git a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts index 608d8f7c2e..b5088d2807 100644 --- a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts +++ b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts @@ -16,49 +16,30 @@ import { Runtime } from '@genkit-ai/tools-common/manager'; import { logger } from '@genkit-ai/tools-common/utils'; -import { select } from '@inquirer/prompts'; import { existsSync, readFileSync } from 'fs'; -import { mkdir, writeFile } from 'fs/promises'; -import path from 'path'; +import { lstat, mkdir, readlink, symlink, writeFile } from 'fs/promises'; +import * as path from 'path'; import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types'; -import { - GENKIT_PROMPT_PATH, - initGenkitFile, - initOrReplaceFile, - updateContentInPlace, -} from '../utils'; +import { GENKIT_PROMPT_PATH, initGenkitFile } from '../utils'; // GEMINI specific paths const GEMINI_DIR = '.gemini'; const GEMINI_SETTINGS_PATH = path.join(GEMINI_DIR, 'settings.json'); -const GEMINI_MD_PATH = path.join('GEMINI.md'); +const GENKIT_MD_SYMLINK_PATH = path.join(GEMINI_DIR, GENKIT_PROMPT_PATH); // GENKIT specific constants -const GENKIT_EXT_DIR = path.join(GEMINI_DIR, 'extensions', 'genkit'); -const GENKIT_MD_REL_PATH = path.join('..', '..', '..', GENKIT_PROMPT_PATH); -const GENKIT_EXTENSION_CONFIG = { - name: 'genkit', - version: '1.0.0', - mcpServers: { - genkit: { - command: 'genkit', - args: ['mcp', '--no-update-notification'], - cwd: '.', - timeout: 30000, - trust: false, - excludeTools: [ - 'run_shell_command(genkit start)', - 'run_shell_command(npx genkit start)', - ], - }, - }, - contextFileName: GENKIT_MD_REL_PATH, +const GENKIT_MCP_CONFIG = { + command: 'genkit', + args: ['mcp', '--no-update-notification'], + cwd: '.', + timeout: 30000, + trust: false, + excludeTools: [ + 'run_shell_command(genkit start)', + 'run_shell_command(npx genkit start)', + ], }; -const EXT_INSTALLATION = 'extension'; -const MD_INSTALLATION = 'geminimd'; -type InstallationType = typeof EXT_INSTALLATION | typeof MD_INSTALLATION; - /** Configuration module for Gemini CLI */ export const gemini: AIToolModule = { name: 'gemini', @@ -71,33 +52,8 @@ export const gemini: AIToolModule = { runtime: Runtime, options?: InitConfigOptions ): Promise { - let installationMethod: InstallationType = EXT_INSTALLATION; - if (!options?.yesMode) { - installationMethod = await select({ - message: 'Select your preferred installation method', - choices: [ - { - name: 'Gemini CLI Extension', - value: 'extension', - description: - 'Use Gemini Extension to install Genkit context in a modular fashion', - }, - { - name: 'GEMINI.md', - value: 'geminimd', - description: 'Incorporate Genkit context within the GEMINI.md file', - }, - ], - }); - } - - if (installationMethod === EXT_INSTALLATION) { - logger.info('Installing as part of GEMINI.md'); - return await installAsExtension(runtime); - } else { - logger.info('Installing as Gemini CLI extension'); - return await installInMdFile(runtime); - } + logger.info('Installing as part of GEMINI.md'); + return await installInMdFile(runtime); }, }; @@ -125,8 +81,19 @@ async function installInMdFile(runtime: Runtime): Promise { if (!existingConfig.mcpServers) { existingConfig.mcpServers = {}; } - existingConfig.mcpServers.genkit = - GENKIT_EXTENSION_CONFIG.mcpServers.genkit; + existingConfig.mcpServers.genkit = GENKIT_MCP_CONFIG; + + if (existingConfig.contextFileName) { + const contextFiles = Array.isArray(existingConfig.contextFileName) + ? [...existingConfig.contextFileName] + : [existingConfig.contextFileName]; + if (!contextFiles.includes('GENKIT.md')) { + contextFiles.push('GENKIT.md'); + } + existingConfig.contextFileName = contextFiles; + } else { + existingConfig.contextFileName = 'GENKIT.md'; + } await writeFile( GEMINI_SETTINGS_PATH, JSON.stringify(existingConfig, null, 2) @@ -140,48 +107,47 @@ async function installInMdFile(runtime: Runtime): Promise { const baseResult = await initGenkitFile(runtime); files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated }); - logger.info('Updating GEMINI.md to include Genkit context'); - const geminiImportTag = `\nGenkit Framework Instructions:\n - @./GENKIT.md\n`; - const { updated: mdUpdated } = await updateContentInPlace( - GEMINI_MD_PATH, - geminiImportTag, - { hash: baseResult.hash } - ); - files.push({ path: GEMINI_MD_PATH, updated: mdUpdated }); - - return { files }; -} - -async function installAsExtension( - runtime: Runtime -): Promise { - const files: AIToolConfigResult['files'] = []; - // Part 1: Generate GENKIT.md file. - const baseResult = await initGenkitFile(runtime); - files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated }); - - // Part 2: Configure the main gemini-extension.json file, and gemini config directory if needed. - logger.info('Configuring extentions files in user workspace'); - await mkdir(GENKIT_EXT_DIR, { recursive: true }); - const extensionPath = path.join(GENKIT_EXT_DIR, 'gemini-extension.json'); - - let extensionUpdated = false; + logger.info('Adding a symlink for GENKIT.md in the .gemini folder'); + // Check if symlink already exists try { - const { updated } = await initOrReplaceFile( - extensionPath, - JSON.stringify(GENKIT_EXTENSION_CONFIG, null, 2) - ); - extensionUpdated = updated; - if (extensionUpdated) { - logger.info( - `Genkit extension for Gemini CLI initialized at ${extensionPath}` + const stats = await lstat(GENKIT_MD_SYMLINK_PATH); + if (stats.isSymbolicLink()) { + // Verify the symlink points to the correct target + const linkTarget = await readlink(GENKIT_MD_SYMLINK_PATH); + const expectedTarget = path.relative(GEMINI_DIR, GENKIT_PROMPT_PATH); + + if (linkTarget === expectedTarget || linkTarget === GENKIT_PROMPT_PATH) { + logger.info( + 'Symlink already exists and points to the correct location' + ); + files.push({ path: GENKIT_MD_SYMLINK_PATH, updated: false }); + } else { + logger.warn( + 'Symlink exists but points to wrong location. Please remove this file and try again if this was not intentional.' + ); + files.push({ path: GENKIT_MD_SYMLINK_PATH, updated: false }); + } + } else { + // File exists but is not a symlink + logger.warn( + `${GENKIT_MD_SYMLINK_PATH} exists but is not a symlink, skipping symlink creation` ); + files.push({ path: GENKIT_MD_SYMLINK_PATH, updated: false }); + } + } catch (error: any) { + if (error.code === 'ENOENT') { + // Symlink doesn't exist, create it + await symlink( + path.relative(GEMINI_DIR, GENKIT_PROMPT_PATH), + GENKIT_MD_SYMLINK_PATH + ); + files.push({ path: GENKIT_MD_SYMLINK_PATH, updated: true }); + } else { + // Some other error occurred + logger.error('Error checking symlink:', error); + throw error; } - } catch (err) { - logger.error(err); - process.exit(1); } - files.push({ path: extensionPath, updated: extensionUpdated }); return { files }; } From 5a7adac8f7e192a81cb3ea1f43b25cbc18dfa9f8 Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Tue, 23 Sep 2025 11:51:09 -0400 Subject: [PATCH 2/3] update to support windows --- .../commands/init-ai-tools/ai-tools/gemini.ts | 138 +++++++++++++----- 1 file changed, 101 insertions(+), 37 deletions(-) diff --git a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts index b5088d2807..4730af2f8a 100644 --- a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts +++ b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts @@ -17,7 +17,14 @@ import { Runtime } from '@genkit-ai/tools-common/manager'; import { logger } from '@genkit-ai/tools-common/utils'; import { existsSync, readFileSync } from 'fs'; -import { lstat, mkdir, readlink, symlink, writeFile } from 'fs/promises'; +import { + copyFile, + lstat, + mkdir, + readlink, + symlink, + writeFile, +} from 'fs/promises'; import * as path from 'path'; import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types'; import { GENKIT_PROMPT_PATH, initGenkitFile } from '../utils'; @@ -107,47 +114,104 @@ async function installInMdFile(runtime: Runtime): Promise { const baseResult = await initGenkitFile(runtime); files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated }); - logger.info('Adding a symlink for GENKIT.md in the .gemini folder'); - // Check if symlink already exists - try { - const stats = await lstat(GENKIT_MD_SYMLINK_PATH); - if (stats.isSymbolicLink()) { - // Verify the symlink points to the correct target - const linkTarget = await readlink(GENKIT_MD_SYMLINK_PATH); - const expectedTarget = path.relative(GEMINI_DIR, GENKIT_PROMPT_PATH); - - if (linkTarget === expectedTarget || linkTarget === GENKIT_PROMPT_PATH) { - logger.info( - 'Symlink already exists and points to the correct location' + // Create link/copy of GENKIT.md in .gemini folder + const linkResult = await createGenkitMdLink(); + files.push(linkResult); + + return { files }; +} + +/** + * Creates a link to GENKIT.md in the .gemini folder. + * On Windows, copies the file instead of creating a symlink. + * On Unix systems, creates a symlink. + */ +async function createGenkitMdLink(): Promise<{ + path: string; + updated: boolean; +}> { + const sourcePath = GENKIT_PROMPT_PATH; + const targetPath = GENKIT_MD_SYMLINK_PATH; + const isWindows = process.platform === 'win32'; + + if (isWindows) { + logger.info( + 'Copying GENKIT.md to .gemini folder (Windows compatibility mode)' + ); + + // Check if file already exists + try { + const stats = await lstat(targetPath); + if (stats.isFile()) { + logger.info('GENKIT.md copy already exists in .gemini folder'); + return { path: targetPath, updated: false }; + } + } catch (error: any) { + if (error.code !== 'ENOENT') { + logger.error('Error checking GENKIT.md copy:', error); + throw error; + } + // File doesn't exist, proceed with copying + } + + // Copy the file + try { + await copyFile(sourcePath, targetPath); + logger.info('Successfully copied GENKIT.md to .gemini folder'); + return { path: targetPath, updated: true }; + } catch (error) { + logger.error('Error copying GENKIT.md:', error); + throw error; + } + } else { + // Unix-like systems: create symlink + logger.info('Adding a symlink for GENKIT.md in the .gemini folder'); + + // Check if symlink already exists + try { + const stats = await lstat(targetPath); + if (stats.isSymbolicLink()) { + // Verify the symlink points to the correct target + const linkTarget = await readlink(targetPath); + + // Resolve the link target to absolute path + const resolvedLinkTarget = path.resolve( + path.dirname(targetPath), + linkTarget ); - files.push({ path: GENKIT_MD_SYMLINK_PATH, updated: false }); + // Resolve the source path to absolute + const resolvedSourcePath = path.resolve(sourcePath); + + // Compare absolute paths + if (resolvedLinkTarget === resolvedSourcePath) { + logger.info( + 'Symlink already exists and points to the correct location' + ); + return { path: targetPath, updated: false }; + } else { + logger.warn( + `Symlink exists but points to wrong location. Expected: ${resolvedSourcePath}, Found: ${resolvedLinkTarget}. Please remove this file and try again if this was not intentional.` + ); + return { path: targetPath, updated: false }; + } } else { + // File exists but is not a symlink logger.warn( - 'Symlink exists but points to wrong location. Please remove this file and try again if this was not intentional.' + `${targetPath} exists but is not a symlink, skipping symlink creation` ); - files.push({ path: GENKIT_MD_SYMLINK_PATH, updated: false }); + return { path: targetPath, updated: false }; + } + } catch (error: any) { + if (error.code === 'ENOENT') { + // Symlink doesn't exist, create it + await symlink(path.relative(GEMINI_DIR, sourcePath), targetPath); + logger.info('Successfully created symlink for GENKIT.md'); + return { path: targetPath, updated: true }; + } else { + // Some other error occurred + logger.error('Error checking symlink:', error); + throw error; } - } else { - // File exists but is not a symlink - logger.warn( - `${GENKIT_MD_SYMLINK_PATH} exists but is not a symlink, skipping symlink creation` - ); - files.push({ path: GENKIT_MD_SYMLINK_PATH, updated: false }); - } - } catch (error: any) { - if (error.code === 'ENOENT') { - // Symlink doesn't exist, create it - await symlink( - path.relative(GEMINI_DIR, GENKIT_PROMPT_PATH), - GENKIT_MD_SYMLINK_PATH - ); - files.push({ path: GENKIT_MD_SYMLINK_PATH, updated: true }); - } else { - // Some other error occurred - logger.error('Error checking symlink:', error); - throw error; } } - - return { files }; } From 5026f682a8daadbad39f1e05d86b9783a7bf8e0c Mon Sep 17 00:00:00 2001 From: ssbushi <66321939+ssbushi@users.noreply.github.com> Date: Tue, 23 Sep 2025 21:31:51 -0400 Subject: [PATCH 3/3] feat(genkit-tools/mcp): Add new MCP tool to fetch usage guide (#3607) --- genkit-tools/cli/src/mcp/server.ts | 4 +- genkit-tools/cli/src/mcp/usage.ts | 60 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 genkit-tools/cli/src/mcp/usage.ts diff --git a/genkit-tools/cli/src/mcp/server.ts b/genkit-tools/cli/src/mcp/server.ts index 9b0d081b01..beccf48dc2 100644 --- a/genkit-tools/cli/src/mcp/server.ts +++ b/genkit-tools/cli/src/mcp/server.ts @@ -21,14 +21,16 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { defineDocsTool } from '../mcp/docs'; import { defineFlowTools } from './flows'; import { defineTraceTools } from './trace'; +import { defineUsageGuideTool } from './usage'; export async function startMcpServer(manager: RuntimeManager) { const server = new McpServer({ name: 'Genkit MCP', - version: '0.0.1', + version: '0.0.2', }); await defineDocsTool(server); + await defineUsageGuideTool(server); defineFlowTools(server, manager); defineTraceTools(server, manager); diff --git a/genkit-tools/cli/src/mcp/usage.ts b/genkit-tools/cli/src/mcp/usage.ts new file mode 100644 index 0000000000..20bc1b3a0c --- /dev/null +++ b/genkit-tools/cli/src/mcp/usage.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { record } from '@genkit-ai/tools-common/utils'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; +import { ContentBlock } from '@modelcontextprotocol/sdk/types'; +import z from 'zod'; +import { McpRunToolEvent } from './analytics.js'; + +import { GENKIT_CONTEXT as GoContext } from '../commands/init-ai-tools/context/go.js'; +import { GENKIT_CONTEXT as JsContext } from '../commands/init-ai-tools/context/nodejs.js'; + +export async function defineUsageGuideTool(server: McpServer) { + server.registerTool( + 'get_usage_guide', + { + title: 'Genkit Instructions', + description: + 'Use this tool to look up the Genkit usage guide before implementing any AI feature', + inputSchema: { + language: z + .enum(['js', 'go']) + .describe('which language this usage guide is for') + .default('js') + .optional(), + }, + }, + async ({ language }) => { + await record(new McpRunToolEvent('get_usage_guide')); + + const content = [] as ContentBlock[]; + if (!language) { + language = 'js'; + } + let text = JsContext; + if (language === 'go') { + text = GoContext; + } + content.push({ + type: 'text', + text, + }); + + return { content }; + } + ); +}