mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-02-01 14:44:29 +00:00
Revert "Revert "Update extension examples"" (#16445)
This commit is contained in:
@@ -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'],
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "hooks-example",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "node ${extensionPath}/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.',
|
||||||
|
);
|
||||||
@@ -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`.
|
||||||
@@ -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 ',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "NodeNext",
|
|
||||||
"moduleResolution": "NodeNext",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"outDir": "./dist"
|
|
||||||
},
|
|
||||||
"include": ["example.ts"]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "skills-example",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
@@ -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! 👋"
|
||||||
Reference in New Issue
Block a user