1/12/2025
No description available
Building Custom Tools and Extensions for Gemini CLI
Gemini CLI's true power lies in its extensibility. By building custom tools and MCP servers, you can create specialized AI workflows tailored to your specific development needs. This guide walks you through creating powerful extensions that integrate seamlessly with Gemini CLI.
Understanding the Extension Ecosystem
Core Extension Types
MCP Servers: Background services that provide tools and resources Custom Tools: Command-line utilities that integrate with Gemini Workflow Scripts: Automated sequences of AI-assisted tasks Project Templates: Standardized project structures with AI context
Building Your First MCP Server
Basic MCP Server Structure
// my-custom-server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
const server = new Server({
name: "my-development-server",
version: "1.0.0",
description: "Custom tools for my development workflow"
});
// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "analyze_code_quality",
description: "Analyzes code quality metrics for a project",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Path to analyze" },
language: { type: "string", description: "Programming language" }
},
required: ["path"]
}
},
{
name: "generate_docs",
description: "Generates documentation from code comments",
inputSchema: {
type: "object",
properties: {
inputPath: { type: "string" },
outputPath: { type: "string" },
format: { type: "string", enum: ["markdown", "html", "json"] }
},
required: ["inputPath", "outputPath"]
}
}
]
}));
// Implement tool handlers
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "analyze_code_quality":
return await analyzeCodeQuality(args.path, args.language);
case "generate_docs":
return await generateDocumentation(args.inputPath, args.outputPath, args.format);
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// Start the server
server.listen();
Implementing Tool Functions
async function analyzeCodeQuality(path: string, language?: string) {
// Implementation using existing tools
const { execSync } = require('child_process');
try {
let result = {};
// Run ESLint for JavaScript/TypeScript
if (language === 'javascript' || language === 'typescript') {
const lintOutput = execSync(`npx eslint ${path} --format json`, { encoding: 'utf8' });
result.linting = JSON.parse(lintOutput);
}
// Run complexity analysis
const complexityOutput = execSync(`npx complexity-report --format json ${path}`, { encoding: 'utf8' });
result.complexity = JSON.parse(complexityOutput);
// Calculate metrics
const metrics = calculateMetrics(result);
return {
content: [{
type: "text",
text: `Code Quality Analysis for ${path}:\n\n${formatMetrics(metrics)}`
}],
isError: false
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error analyzing code quality: ${error.message}`
}],
isError: true
};
}
}
async function generateDocumentation(inputPath: string, outputPath: string, format: string) {
// Documentation generation logic
const fs = require('fs').promises;
const path = require('path');
try {
// Read source files
const files = await getSourceFiles(inputPath);
const docs = [];
for (const file of files) {
const content = await fs.readFile(file, 'utf8');
const extracted = extractDocumentation(content);
docs.push({
file: path.relative(inputPath, file),
documentation: extracted
});
}
// Generate output
const output = formatDocumentation(docs, format);
await fs.writeFile(outputPath, output);
return {
content: [{
type: "text",
text: `Documentation generated successfully at ${outputPath}`
}],
isError: false
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error generating documentation: ${error.message}`
}],
isError: true
};
}
}
Advanced MCP Server Features
Resource Management
// Add resource handling for file access
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "file:///project/metrics",
name: "Project Metrics",
description: "Real-time project quality metrics",
mimeType: "application/json"
}
]
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === "file:///project/metrics") {
const metrics = await getCurrentMetrics();
return {
contents: [{
uri,
mimeType: "application/json",
text: JSON.stringify(metrics, null, 2)
}]
};
}
throw new Error(`Unknown resource: ${uri}`);
});
Configuration and State
interface ServerConfig {
projectPath: string;
cacheDirectory: string;
enableMetrics: boolean;
customRules: string[];
}
class DevelopmentServer {
private config: ServerConfig;
private cache: Map<string, any> = new Map();
constructor(config: ServerConfig) {
this.config = config;
}
async initialize() {
// Setup cache directory
await fs.mkdir(this.config.cacheDirectory, { recursive: true });
// Load custom rules
await this.loadCustomRules();
}
private async loadCustomRules() {
// Implementation for loading custom analysis rules
}
}
Creating Specialized Development Tools
1. Database Schema Analyzer
// database-mcp-server.ts
const tools = [
{
name: "analyze_schema",
description: "Analyzes database schema and suggests optimizations",
inputSchema: {
type: "object",
properties: {
connectionString: { type: "string" },
schemaName: { type: "string" }
}
}
},
{
name: "generate_migrations",
description: "Generates migration scripts for schema changes",
inputSchema: {
type: "object",
properties: {
fromSchema: { type: "string" },
toSchema: { type: "string" },
migrationName: { type: "string" }
}
}
}
];
2. API Documentation Generator
// api-docs-server.ts
const tools = [
{
name: "scan_api_routes",
description: "Scans codebase for API routes and extracts documentation",
inputSchema: {
type: "object",
properties: {
routesPath: { type: "string" },
framework: { type: "string", enum: ["express", "fastify", "koa"] }
}
}
},
{
name: "generate_openapi",
description: "Generates OpenAPI specification from route analysis",
inputSchema: {
type: "object",
properties: {
routeData: { type: "object" },
outputFormat: { type: "string", enum: ["yaml", "json"] }
}
}
}
];
3. Performance Monitor
// performance-server.ts
const tools = [
{
name: "profile_application",
description: "Profiles application performance and identifies bottlenecks",
inputSchema: {
type: "object",
properties: {
command: { type: "string" },
duration: { type: "number", default: 30 },
outputFile: { type: "string" }
}
}
}
];
Workflow Automation Scripts
Automated Code Review Script
#!/bin/bash
# code-review.sh - Automated AI code review workflow
PROJECT_PATH=$1
REVIEW_TYPE=${2:-"full"}
# Start Gemini CLI with custom prompt
gemini -p "
Perform a ${REVIEW_TYPE} code review of the project at ${PROJECT_PATH}.
Focus on:
1. Security vulnerabilities
2. Performance issues
3. Code quality and maintainability
4. Test coverage gaps
5. Documentation needs
Use the following tools:
- analyze_code_quality for metrics
- scan_api_routes for API analysis
- profile_application for performance
Generate a comprehensive report with actionable recommendations.
"
Project Scaffolding Automation
#!/bin/bash
# scaffold-project.sh - AI-assisted project creation
PROJECT_NAME=$1
PROJECT_TYPE=$2
TECH_STACK=$3
gemini -p "
Create a new ${PROJECT_TYPE} project named '${PROJECT_NAME}' using ${TECH_STACK}.
Requirements:
1. Generate proper project structure
2. Configure build tools and linting
3. Set up testing framework
4. Create initial documentation
5. Add CI/CD configuration
6. Include security best practices
Use generate_docs tool for documentation and analyze_code_quality for validation.
"
Integration with Development Environment
VS Code Extension Integration
// .vscode/settings.json
{
"gemini-cli.mcpServers": [
{
"name": "my-development-server",
"command": "node",
"args": ["./tools/my-custom-server.js"],
"env": {
"PROJECT_PATH": "${workspaceFolder}"
}
}
],
"gemini-cli.customPrompts": [
{
"name": "Code Review",
"prompt": "Review the currently open file for issues and improvements",
"keybinding": "ctrl+shift+r"
}
]
}
Git Hooks Integration
#!/bin/bash
# .git/hooks/pre-commit
# Run AI-assisted code review before commit
echo "Running AI code review..."
CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
if [ ! -z "$CHANGED_FILES" ]; then
gemini -p "
Review these files before commit:
$(echo "$CHANGED_FILES" | sed 's/^/@/')
Check for:
- Security issues
- Code style violations
- Potential bugs
- Missing tests
If issues found, prevent commit and suggest fixes.
"
fi
Testing and Deployment
MCP Server Testing
// test/mcp-server.test.ts
import { TestClient } from '@modelcontextprotocol/sdk/test/index.js';
import { myServer } from '../src/server.js';
describe('My Development Server', () => {
let client: TestClient;
beforeEach(async () => {
client = new TestClient(myServer);
await client.connect();
});
test('should list available tools', async () => {
const response = await client.listTools();
expect(response.tools).toHaveLength(2);
expect(response.tools[0].name).toBe('analyze_code_quality');
});
test('should analyze code quality', async () => {
const response = await client.callTool('analyze_code_quality', {
path: './test/fixtures/sample-code.js',
language: 'javascript'
});
expect(response.isError).toBe(false);
expect(response.content[0].text).toContain('Code Quality Analysis');
});
});
Distribution and Packaging
// package.json
{
"name": "my-gemini-cli-extension",
"version": "1.0.0",
"description": "Custom development tools for Gemini CLI",
"main": "dist/server.js",
"bin": {
"my-dev-server": "dist/server.js"
},
"scripts": {
"build": "tsc",
"test": "jest",
"start": "node dist/server.js"
},
"keywords": ["gemini-cli", "mcp", "development", "tools"],
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
}
}
Best Practices for Custom Tools
1. Error Handling
Always provide meaningful error messages and graceful degradation:
try {
const result = await performOperation();
return { content: [{ type: "text", text: result }], isError: false };
} catch (error) {
return {
content: [{
type: "text",
text: `Operation failed: ${error.message}\n\nTroubleshooting:\n1. Check file permissions\n2. Verify dependencies\n3. Review configuration`
}],
isError: true
};
}
2. Performance Optimization
Implement caching and async operations:
class PerformantServer {
private cache = new Map<string, { data: any, timestamp: number }>();
private readonly CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async getCachedResult(key: string, generator: () => Promise<any>) {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
return cached.data;
}
const data = await generator();
this.cache.set(key, { data, timestamp: Date.now() });
return data;
}
}
3. Security Considerations
Validate inputs and limit file system access:
function validatePath(inputPath: string, allowedPaths: string[]) {
const resolved = path.resolve(inputPath);
const isAllowed = allowedPaths.some(allowed =>
resolved.startsWith(path.resolve(allowed))
);
if (!isAllowed) {
throw new Error(`Access denied: ${inputPath} is outside allowed directories`);
}
return resolved;
}
Conclusion
Building custom tools for Gemini CLI opens up unlimited possibilities for AI-assisted development. By creating specialized MCP servers, you can:
- Automate repetitive tasks with AI assistance
- Integrate domain-specific knowledge into your workflow
- Standardize development practices across your team
- Create powerful debugging and analysis tools
Start with simple tools and gradually build more sophisticated extensions as your needs evolve. The investment in custom tooling will pay dividends in improved productivity and code quality.
Ready to start building? Check out the MCP Server Template and join our Developer Community for support and inspiration.