From 8437ce940a1cc478c65630c05bc9e015271c572a Mon Sep 17 00:00:00 2001 From: christine betts Date: Mon, 12 Jan 2026 15:53:22 -0500 Subject: [PATCH] Revert "Update extension examples" (#16442) --- 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 ----- .../examples/mcp-server/example.test.ts | 135 ++++++++++++++++++ .../mcp-server/{example.js => example.ts} | 0 .../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 - packages/cli/tsconfig.json | 2 +- 13 files changed, 157 insertions(+), 84 deletions(-) delete mode 100644 packages/cli/src/commands/extensions/examples/hooks/gemini-extension.json delete mode 100644 packages/cli/src/commands/extensions/examples/hooks/hooks/hooks.json delete mode 100644 packages/cli/src/commands/extensions/examples/hooks/scripts/on-start.js delete mode 100644 packages/cli/src/commands/extensions/examples/mcp-server/README.md create mode 100644 packages/cli/src/commands/extensions/examples/mcp-server/example.test.ts rename packages/cli/src/commands/extensions/examples/mcp-server/{example.js => example.ts} (100%) create mode 100644 packages/cli/src/commands/extensions/examples/mcp-server/tsconfig.json delete mode 100644 packages/cli/src/commands/extensions/examples/skills/gemini-extension.json delete mode 100644 packages/cli/src/commands/extensions/examples/skills/skills/greeter/SKILL.md diff --git a/eslint.config.js b/eslint.config.js index 959fbc5edb..c2d0d3b69b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -301,16 +301,6 @@ 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 deleted file mode 100644 index 708e986346..0000000000 --- a/packages/cli/src/commands/extensions/examples/hooks/gemini-extension.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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 deleted file mode 100644 index f1af86d980..0000000000 --- a/packages/cli/src/commands/extensions/examples/hooks/hooks/hooks.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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 deleted file mode 100644 index 1f426f9a2f..0000000000 --- a/packages/cli/src/commands/extensions/examples/hooks/scripts/on-start.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @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 deleted file mode 100644 index 3ca50977ed..0000000000 --- a/packages/cli/src/commands/extensions/examples/mcp-server/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# 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.test.ts b/packages/cli/src/commands/extensions/examples/mcp-server/example.test.ts new file mode 100644 index 0000000000..5f5660df76 --- /dev/null +++ b/packages/cli/src/commands/extensions/examples/mcp-server/example.test.ts @@ -0,0 +1,135 @@ +/** + * @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/example.js b/packages/cli/src/commands/extensions/examples/mcp-server/example.ts similarity index 100% rename from packages/cli/src/commands/extensions/examples/mcp-server/example.js rename to packages/cli/src/commands/extensions/examples/mcp-server/example.ts 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 25cea93411..62561dbf8d 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}${/}example.js"], + "args": ["${extensionPath}${/}dist${/}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 ddb2959c38..45aa203ef3 100644 --- a/packages/cli/src/commands/extensions/examples/mcp-server/package.json +++ b/packages/cli/src/commands/extensions/examples/mcp-server/package.json @@ -4,6 +4,13 @@ "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 new file mode 100644 index 0000000000..b94585edce --- /dev/null +++ b/packages/cli/src/commands/extensions/examples/mcp-server/tsconfig.json @@ -0,0 +1,13 @@ +{ + "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 deleted file mode 100644 index 2674ef9e0f..0000000000 --- a/packages/cli/src/commands/extensions/examples/skills/gemini-extension.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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 deleted file mode 100644 index 24da110909..0000000000 --- a/packages/cli/src/commands/extensions/examples/skills/skills/greeter/SKILL.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -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! 👋" diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index b06787f0e6..e361d7ffe0 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -13,6 +13,6 @@ "src/**/*.json", "./package.json" ], - "exclude": ["node_modules", "dist", "src/commands/extensions/examples"], + "exclude": ["node_modules", "dist"], "references": [{ "path": "../core" }] }