Revert "Revert "Update extension examples"" (#16445)

This commit is contained in:
christine betts
2026-01-20 12:14:46 -05:00
committed by GitHub
parent 88df6210eb
commit 85b17166a5
12 changed files with 83 additions and 156 deletions

View File

@@ -304,6 +304,16 @@ export default tseslint.config(
'@typescript-eslint/no-require-imports': 'off', '@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 // extra settings for scripts that we run directly with node
{ {
files: ['packages/vscode-ide-companion/scripts/**/*.js'], files: ['packages/vscode-ide-companion/scripts/**/*.js'],

View File

@@ -0,0 +1,4 @@
{
"name": "hooks-example",
"version": "1.0.0"
}

View File

@@ -0,0 +1,14 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "node ${extensionPath}/scripts/on-start.js"
}
]
}
]
}
}

View File

@@ -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.',
);

View File

@@ -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`.

View File

@@ -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 ',
},
},
],
});
});
});
});

View File

@@ -4,7 +4,7 @@
"mcpServers": { "mcpServers": {
"nodeServer": { "nodeServer": {
"command": "node", "command": "node",
"args": ["${extensionPath}${/}dist${/}example.js"], "args": ["${extensionPath}${/}example.js"],
"cwd": "${extensionPath}" "cwd": "${extensionPath}"
} }
} }

View File

@@ -4,13 +4,6 @@
"description": "Example MCP Server for Gemini CLI Extension", "description": "Example MCP Server for Gemini CLI Extension",
"type": "module", "type": "module",
"main": "example.js", "main": "example.js",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"typescript": "~5.4.5",
"@types/node": "^20.11.25"
},
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0", "@modelcontextprotocol/sdk": "^1.23.0",
"zod": "^3.22.4" "zod": "^3.22.4"

View File

@@ -1,13 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist"
},
"include": ["example.ts"]
}

View File

@@ -0,0 +1,4 @@
{
"name": "skills-example",
"version": "1.0.0"
}

View File

@@ -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! 👋"