From 85b17166a53a4698ecd21895f4c7bb6e8b2337e5 Mon Sep 17 00:00:00 2001 From: christine betts Date: Tue, 20 Jan 2026 12:14:46 -0500 Subject: [PATCH] Revert "Revert "Update extension examples"" (#16445) --- eslint.config.js | 10 ++ .../examples/hooks/gemini-extension.json | 4 + .../examples/hooks/hooks/hooks.json | 14 ++ .../examples/hooks/scripts/on-start.js | 8 ++ .../extensions/examples/mcp-server/README.md | 35 +++++ .../mcp-server/{example.ts => example.js} | 0 .../examples/mcp-server/example.test.ts | 135 ------------------ .../examples/mcp-server/gemini-extension.json | 2 +- .../examples/mcp-server/package.json | 7 - .../examples/mcp-server/tsconfig.json | 13 -- .../examples/skills/gemini-extension.json | 4 + .../examples/skills/skills/greeter/SKILL.md | 7 + 12 files changed, 83 insertions(+), 156 deletions(-) create mode 100644 packages/cli/src/commands/extensions/examples/hooks/gemini-extension.json create mode 100644 packages/cli/src/commands/extensions/examples/hooks/hooks/hooks.json create mode 100644 packages/cli/src/commands/extensions/examples/hooks/scripts/on-start.js create mode 100644 packages/cli/src/commands/extensions/examples/mcp-server/README.md rename packages/cli/src/commands/extensions/examples/mcp-server/{example.ts => example.js} (100%) delete mode 100644 packages/cli/src/commands/extensions/examples/mcp-server/example.test.ts delete mode 100644 packages/cli/src/commands/extensions/examples/mcp-server/tsconfig.json create mode 100644 packages/cli/src/commands/extensions/examples/skills/gemini-extension.json create mode 100644 packages/cli/src/commands/extensions/examples/skills/skills/greeter/SKILL.md diff --git a/eslint.config.js b/eslint.config.js index 3dcb7d8903..301dd7cf5d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -304,6 +304,16 @@ export default tseslint.config( '@typescript-eslint/no-require-imports': 'off', }, }, + // Examples should have access to standard globals like fetch + { + files: ['packages/cli/src/commands/extensions/examples/**/*.js'], + languageOptions: { + globals: { + ...globals.node, + fetch: 'readonly', + }, + }, + }, // extra settings for scripts that we run directly with node { files: ['packages/vscode-ide-companion/scripts/**/*.js'], diff --git a/packages/cli/src/commands/extensions/examples/hooks/gemini-extension.json b/packages/cli/src/commands/extensions/examples/hooks/gemini-extension.json new file mode 100644 index 0000000000..708e986346 --- /dev/null +++ b/packages/cli/src/commands/extensions/examples/hooks/gemini-extension.json @@ -0,0 +1,4 @@ +{ + "name": "hooks-example", + "version": "1.0.0" +} diff --git a/packages/cli/src/commands/extensions/examples/hooks/hooks/hooks.json b/packages/cli/src/commands/extensions/examples/hooks/hooks/hooks.json new file mode 100644 index 0000000000..f1af86d980 --- /dev/null +++ b/packages/cli/src/commands/extensions/examples/hooks/hooks/hooks.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "node ${extensionPath}/scripts/on-start.js" + } + ] + } + ] + } +} diff --git a/packages/cli/src/commands/extensions/examples/hooks/scripts/on-start.js b/packages/cli/src/commands/extensions/examples/hooks/scripts/on-start.js new file mode 100644 index 0000000000..1f426f9a2f --- /dev/null +++ b/packages/cli/src/commands/extensions/examples/hooks/scripts/on-start.js @@ -0,0 +1,8 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +console.log( + 'Session Started! This is running from a script in the hooks-example extension.', +); diff --git a/packages/cli/src/commands/extensions/examples/mcp-server/README.md b/packages/cli/src/commands/extensions/examples/mcp-server/README.md new file mode 100644 index 0000000000..3ca50977ed --- /dev/null +++ b/packages/cli/src/commands/extensions/examples/mcp-server/README.md @@ -0,0 +1,35 @@ +# MCP Server Example + +This is a basic example of an MCP (Model Context Protocol) server used as a +Gemini CLI extension. It demonstrates how to expose tools and prompts to the +Gemini CLI. + +## Description + +The contents of this directory are a valid MCP server implementation using the +`@modelcontextprotocol/sdk`. It exposes: + +- A tool `fetch_posts` that mock-fetches posts. +- A prompt `poem-writer`. + +## Structure + +- `example.js`: The main server entry point. +- `gemini-extension.json`: The configuration file that tells Gemini CLI how to + use this extension. +- `package.json`: Helper for dependencies. + +## How to Use + +1. Navigate to this directory: + + ```bash + cd packages/cli/src/commands/extensions/examples/mcp-server + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +This example is typically used by `gemini extensions new`. diff --git a/packages/cli/src/commands/extensions/examples/mcp-server/example.ts b/packages/cli/src/commands/extensions/examples/mcp-server/example.js similarity index 100% rename from packages/cli/src/commands/extensions/examples/mcp-server/example.ts rename to packages/cli/src/commands/extensions/examples/mcp-server/example.js diff --git a/packages/cli/src/commands/extensions/examples/mcp-server/example.test.ts b/packages/cli/src/commands/extensions/examples/mcp-server/example.test.ts deleted file mode 100644 index 5f5660df76..0000000000 --- a/packages/cli/src/commands/extensions/examples/mcp-server/example.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * @license - * Copyright 2025 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { z } from 'zod'; - -// Mock the MCP server and transport -const mockRegisterTool = vi.fn(); -const mockRegisterPrompt = vi.fn(); -const mockConnect = vi.fn(); - -vi.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({ - McpServer: vi.fn().mockImplementation(() => ({ - registerTool: mockRegisterTool, - registerPrompt: mockRegisterPrompt, - connect: mockConnect, - })), -})); - -vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ - StdioServerTransport: vi.fn(), -})); - -describe('MCP Server Example', () => { - beforeEach(async () => { - // Dynamically import the server setup after mocks are in place - await import('./example.js'); - }); - - afterEach(() => { - vi.clearAllMocks(); - vi.resetModules(); - }); - - it('should create an McpServer with the correct name and version', () => { - expect(McpServer).toHaveBeenCalledWith({ - name: 'prompt-server', - version: '1.0.0', - }); - }); - - it('should register the "fetch_posts" tool', () => { - expect(mockRegisterTool).toHaveBeenCalledWith( - 'fetch_posts', - { - description: 'Fetches a list of posts from a public API.', - inputSchema: z.object({}).shape, - }, - expect.any(Function), - ); - }); - - it('should register the "poem-writer" prompt', () => { - expect(mockRegisterPrompt).toHaveBeenCalledWith( - 'poem-writer', - { - title: 'Poem Writer', - description: 'Write a nice haiku', - argsSchema: expect.any(Object), - }, - expect.any(Function), - ); - }); - - it('should connect the server to an StdioServerTransport', () => { - expect(StdioServerTransport).toHaveBeenCalled(); - expect(mockConnect).toHaveBeenCalledWith(expect.any(StdioServerTransport)); - }); - - describe('fetch_posts tool implementation', () => { - it('should fetch posts and return a formatted response', async () => { - const mockPosts = [ - { id: 1, title: 'Post 1' }, - { id: 2, title: 'Post 2' }, - ]; - global.fetch = vi.fn().mockResolvedValue({ - json: vi.fn().mockResolvedValue(mockPosts), - }); - - const toolFn = mockRegisterTool.mock.calls[0][2]; - const result = await toolFn(); - - expect(global.fetch).toHaveBeenCalledWith( - 'https://jsonplaceholder.typicode.com/posts', - ); - expect(result).toEqual({ - content: [ - { - type: 'text', - text: JSON.stringify({ posts: mockPosts }), - }, - ], - }); - }); - }); - - describe('poem-writer prompt implementation', () => { - it('should generate a prompt with a title', () => { - const promptFn = mockRegisterPrompt.mock.calls[0][2]; - const result = promptFn({ title: 'My Poem' }); - expect(result).toEqual({ - messages: [ - { - role: 'user', - content: { - type: 'text', - text: 'Write a haiku called My Poem. Note that a haiku is 5 syllables followed by 7 syllables followed by 5 syllables ', - }, - }, - ], - }); - }); - - it('should generate a prompt with a title and mood', () => { - const promptFn = mockRegisterPrompt.mock.calls[0][2]; - const result = promptFn({ title: 'My Poem', mood: 'sad' }); - expect(result).toEqual({ - messages: [ - { - role: 'user', - content: { - type: 'text', - text: 'Write a haiku with the mood sad called My Poem. Note that a haiku is 5 syllables followed by 7 syllables followed by 5 syllables ', - }, - }, - ], - }); - }); - }); -}); diff --git a/packages/cli/src/commands/extensions/examples/mcp-server/gemini-extension.json b/packages/cli/src/commands/extensions/examples/mcp-server/gemini-extension.json index 62561dbf8d..25cea93411 100644 --- a/packages/cli/src/commands/extensions/examples/mcp-server/gemini-extension.json +++ b/packages/cli/src/commands/extensions/examples/mcp-server/gemini-extension.json @@ -4,7 +4,7 @@ "mcpServers": { "nodeServer": { "command": "node", - "args": ["${extensionPath}${/}dist${/}example.js"], + "args": ["${extensionPath}${/}example.js"], "cwd": "${extensionPath}" } } diff --git a/packages/cli/src/commands/extensions/examples/mcp-server/package.json b/packages/cli/src/commands/extensions/examples/mcp-server/package.json index 45aa203ef3..ddb2959c38 100644 --- a/packages/cli/src/commands/extensions/examples/mcp-server/package.json +++ b/packages/cli/src/commands/extensions/examples/mcp-server/package.json @@ -4,13 +4,6 @@ "description": "Example MCP Server for Gemini CLI Extension", "type": "module", "main": "example.js", - "scripts": { - "build": "tsc" - }, - "devDependencies": { - "typescript": "~5.4.5", - "@types/node": "^20.11.25" - }, "dependencies": { "@modelcontextprotocol/sdk": "^1.23.0", "zod": "^3.22.4" diff --git a/packages/cli/src/commands/extensions/examples/mcp-server/tsconfig.json b/packages/cli/src/commands/extensions/examples/mcp-server/tsconfig.json deleted file mode 100644 index b94585edce..0000000000 --- a/packages/cli/src/commands/extensions/examples/mcp-server/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "outDir": "./dist" - }, - "include": ["example.ts"] -} diff --git a/packages/cli/src/commands/extensions/examples/skills/gemini-extension.json b/packages/cli/src/commands/extensions/examples/skills/gemini-extension.json new file mode 100644 index 0000000000..2674ef9e0f --- /dev/null +++ b/packages/cli/src/commands/extensions/examples/skills/gemini-extension.json @@ -0,0 +1,4 @@ +{ + "name": "skills-example", + "version": "1.0.0" +} diff --git a/packages/cli/src/commands/extensions/examples/skills/skills/greeter/SKILL.md b/packages/cli/src/commands/extensions/examples/skills/skills/greeter/SKILL.md new file mode 100644 index 0000000000..24da110909 --- /dev/null +++ b/packages/cli/src/commands/extensions/examples/skills/skills/greeter/SKILL.md @@ -0,0 +1,7 @@ +--- +name: greeter +description: A friendly greeter skill +--- + +You are a friendly greeter. When the user says "hello" or asks for a greeting, +you should reply with: "Greetings from the skills-example extension! 👋"