This is page 1 of 6. Use http://codebase.md/bsmi021/mcp-gemini-server?page={x} to view the full context.
# Directory Structure
```
├── .env.example
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── review-prompt.txt
├── scripts
│ ├── gemini-review.sh
│ └── run-with-health-check.sh
├── smithery.yaml
├── src
│ ├── config
│ │ └── ConfigurationManager.ts
│ ├── createServer.ts
│ ├── index.ts
│ ├── resources
│ │ └── system-prompt.md
│ ├── server.ts
│ ├── services
│ │ ├── ExampleService.ts
│ │ ├── gemini
│ │ │ ├── GeminiCacheService.ts
│ │ │ ├── GeminiChatService.ts
│ │ │ ├── GeminiContentService.ts
│ │ │ ├── GeminiGitDiffService.ts
│ │ │ ├── GeminiPromptTemplates.ts
│ │ │ ├── GeminiTypes.ts
│ │ │ ├── GeminiUrlContextService.ts
│ │ │ ├── GeminiValidationSchemas.ts
│ │ │ ├── GitHubApiService.ts
│ │ │ ├── GitHubUrlParser.ts
│ │ │ └── ModelMigrationService.ts
│ │ ├── GeminiService.ts
│ │ ├── index.ts
│ │ ├── mcp
│ │ │ ├── index.ts
│ │ │ └── McpClientService.ts
│ │ ├── ModelSelectionService.ts
│ │ ├── session
│ │ │ ├── index.ts
│ │ │ ├── InMemorySessionStore.ts
│ │ │ ├── SessionStore.ts
│ │ │ └── SQLiteSessionStore.ts
│ │ └── SessionService.ts
│ ├── tools
│ │ ├── exampleToolParams.ts
│ │ ├── geminiCacheParams.ts
│ │ ├── geminiCacheTool.ts
│ │ ├── geminiChatParams.ts
│ │ ├── geminiChatTool.ts
│ │ ├── geminiCodeReviewParams.ts
│ │ ├── geminiCodeReviewTool.ts
│ │ ├── geminiGenerateContentConsolidatedParams.ts
│ │ ├── geminiGenerateContentConsolidatedTool.ts
│ │ ├── geminiGenerateImageParams.ts
│ │ ├── geminiGenerateImageTool.ts
│ │ ├── geminiGenericParamSchemas.ts
│ │ ├── geminiRouteMessageParams.ts
│ │ ├── geminiRouteMessageTool.ts
│ │ ├── geminiUrlAnalysisTool.ts
│ │ ├── index.ts
│ │ ├── mcpClientParams.ts
│ │ ├── mcpClientTool.ts
│ │ ├── registration
│ │ │ ├── index.ts
│ │ │ ├── registerAllTools.ts
│ │ │ ├── ToolAdapter.ts
│ │ │ └── ToolRegistry.ts
│ │ ├── schemas
│ │ │ ├── BaseToolSchema.ts
│ │ │ ├── CommonSchemas.ts
│ │ │ ├── index.ts
│ │ │ ├── ToolSchemas.ts
│ │ │ └── writeToFileParams.ts
│ │ └── writeToFileTool.ts
│ ├── types
│ │ ├── exampleServiceTypes.ts
│ │ ├── geminiServiceTypes.ts
│ │ ├── gitdiff-parser.d.ts
│ │ ├── googleGenAI.d.ts
│ │ ├── googleGenAITypes.ts
│ │ ├── index.ts
│ │ ├── micromatch.d.ts
│ │ ├── modelcontextprotocol-sdk.d.ts
│ │ ├── node-fetch.d.ts
│ │ └── serverTypes.ts
│ └── utils
│ ├── errors.ts
│ ├── filePathSecurity.ts
│ ├── FileSecurityService.ts
│ ├── geminiErrors.ts
│ ├── healthCheck.ts
│ ├── index.ts
│ ├── logger.ts
│ ├── RetryService.ts
│ ├── ToolError.ts
│ └── UrlSecurityService.ts
├── tests
│ ├── .env.test.example
│ ├── basic-router.test.vitest.ts
│ ├── e2e
│ │ ├── clients
│ │ │ └── mcp-test-client.ts
│ │ ├── README.md
│ │ └── streamableHttpTransport.test.vitest.ts
│ ├── integration
│ │ ├── dummyMcpServerSse.ts
│ │ ├── dummyMcpServerStdio.ts
│ │ ├── geminiRouterIntegration.test.vitest.ts
│ │ ├── mcpClientIntegration.test.vitest.ts
│ │ ├── multiModelIntegration.test.vitest.ts
│ │ └── urlContextIntegration.test.vitest.ts
│ ├── tsconfig.test.json
│ ├── unit
│ │ ├── config
│ │ │ └── ConfigurationManager.multimodel.test.vitest.ts
│ │ ├── server
│ │ │ └── transportLogic.test.vitest.ts
│ │ ├── services
│ │ │ ├── gemini
│ │ │ │ ├── GeminiChatService.test.vitest.ts
│ │ │ │ ├── GeminiGitDiffService.test.vitest.ts
│ │ │ │ ├── geminiImageGeneration.test.vitest.ts
│ │ │ │ ├── GeminiPromptTemplates.test.vitest.ts
│ │ │ │ ├── GeminiUrlContextService.test.vitest.ts
│ │ │ │ ├── GeminiValidationSchemas.test.vitest.ts
│ │ │ │ ├── GitHubApiService.test.vitest.ts
│ │ │ │ ├── GitHubUrlParser.test.vitest.ts
│ │ │ │ └── ThinkingBudget.test.vitest.ts
│ │ │ ├── mcp
│ │ │ │ └── McpClientService.test.vitest.ts
│ │ │ ├── ModelSelectionService.test.vitest.ts
│ │ │ └── session
│ │ │ └── SQLiteSessionStore.test.vitest.ts
│ │ ├── tools
│ │ │ ├── geminiCacheTool.test.vitest.ts
│ │ │ ├── geminiChatTool.test.vitest.ts
│ │ │ ├── geminiCodeReviewTool.test.vitest.ts
│ │ │ ├── geminiGenerateContentConsolidatedTool.test.vitest.ts
│ │ │ ├── geminiGenerateImageTool.test.vitest.ts
│ │ │ ├── geminiRouteMessageTool.test.vitest.ts
│ │ │ ├── mcpClientTool.test.vitest.ts
│ │ │ ├── mcpToolsTests.test.vitest.ts
│ │ │ └── schemas
│ │ │ ├── BaseToolSchema.test.vitest.ts
│ │ │ ├── ToolParamSchemas.test.vitest.ts
│ │ │ └── ToolSchemas.test.vitest.ts
│ │ └── utils
│ │ ├── errors.test.vitest.ts
│ │ ├── FileSecurityService.test.vitest.ts
│ │ ├── FileSecurityService.vitest.ts
│ │ ├── FileSecurityServiceBasics.test.vitest.ts
│ │ ├── healthCheck.test.vitest.ts
│ │ ├── RetryService.test.vitest.ts
│ │ └── UrlSecurityService.test.vitest.ts
│ └── utils
│ ├── assertions.ts
│ ├── debug-error.ts
│ ├── env-check.ts
│ ├── environment.ts
│ ├── error-helpers.ts
│ ├── express-mocks.ts
│ ├── integration-types.ts
│ ├── mock-types.ts
│ ├── test-fixtures.ts
│ ├── test-generators.ts
│ ├── test-setup.ts
│ └── vitest.d.ts
├── tsconfig.json
├── tsconfig.test.json
├── vitest-globals.d.ts
├── vitest.config.ts
└── vitest.setup.ts
```
# Files
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
```json
{
"semi": true,
"trailingComma": "es5",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"endOfLine": "lf"
}
```
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
```
# Compiled output
dist/
build/
# Node modules
node_modules/
# Generated files
*.generated.*
*.d.ts.map
# Coverage output
coverage/
# Other build artifacts
.next/
.nuxt/
# Documentation and config files
*.md
*.txt
tsconfig*.json
package-lock.json
*.yaml
*.yml
.DS_Store
LICENSE
# Environment and config
.env*
.prettierrc*
.husky/
.git/
```
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
```json
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"prettier"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"prettier/prettier": "warn",
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-explicit-any": "warn",
"no-console": "off"
},
"env": {
"node": true,
"es2022": true
},
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"overrides": [
{
"files": ["tests/**/*.ts", "tests/**/*.vitest.ts"],
"rules": {
"@typescript-eslint/no-explicit-any": "off"
}
}
]
}
```
--------------------------------------------------------------------------------
/tests/.env.test.example:
--------------------------------------------------------------------------------
```
# Test environment configuration
# Copy this file to .env.test and fill in your API keys and other settings
# Required: Google Gemini API key from Google AI Studio
GOOGLE_GEMINI_API_KEY=your_api_key_here
# Optional: Default model to use for tests (defaults to gemini-1.5-flash)
GOOGLE_GEMINI_MODEL=gemini-1.5-flash
# Optional: Base directory for file tests (defaults to current directory)
GEMINI_SAFE_FILE_BASE_DIR=/Users/nicobailon/Documents/development/mcp-gemini-server/tests/resources
# Optional: Image generation settings
GOOGLE_GEMINI_IMAGE_RESOLUTION=1024x1024
GOOGLE_GEMINI_MAX_IMAGE_SIZE_MB=10
GOOGLE_GEMINI_SUPPORTED_IMAGE_FORMATS=["image/jpeg","image/png","image/webp"]
# Optional: Model-specific configuration
# Set this to configure specific models for image generation tests
GOOGLE_GEMINI_IMAGE_MODEL=gemini-2.0-flash-exp-image-generation
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
docs/
.clinerules
.cursor/
.husky/
.vscode/
.idea/
.DS_Store
*workspace*
*.md
!README.md
**/.claude/settings.local.json
# Session data
data/
*.db
*.db-wal
*.db-shm
.env
```
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
# Required API Configuration
GOOGLE_GEMINI_API_KEY=your_api_key_here
# Required for Production (unless NODE_ENV=test)
MCP_SERVER_HOST=localhost
MCP_SERVER_PORT=8080
# Generate this token securely using the methods in Installation step 4
# e.g., node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
MCP_CONNECTION_TOKEN=your_generated_secure_token_from_step_4
# Optional API Configuration
GOOGLE_GEMINI_MODEL=gemini-1.5-pro-latest
GOOGLE_GEMINI_DEFAULT_THINKING_BUDGET=4096
# Security Configuration
ALLOWED_OUTPUT_PATHS=/var/opt/mcp-gemini-server/outputs,/var/opt/mcp-gemini-server/tool_data # For mcpCallServerTool and writeToFileTool
# URL Context Configuration
GOOGLE_GEMINI_ENABLE_URL_CONTEXT=true # Enable URL context features
GOOGLE_GEMINI_URL_MAX_COUNT=20 # Maximum URLs per request
GOOGLE_GEMINI_URL_MAX_CONTENT_KB=100 # Maximum content size per URL in KB
GOOGLE_GEMINI_URL_FETCH_TIMEOUT_MS=10000 # Fetch timeout per URL in milliseconds
GOOGLE_GEMINI_URL_ALLOWED_DOMAINS=* # Allowed domains (* for all, or comma-separated list)
GOOGLE_GEMINI_URL_BLOCKLIST=malicious.com,spam.net # Blocked domains (comma-separated)
GOOGLE_GEMINI_URL_CONVERT_TO_MARKDOWN=true # Convert HTML to markdown
GOOGLE_GEMINI_URL_INCLUDE_METADATA=true # Include URL metadata in context
GOOGLE_GEMINI_URL_ENABLE_CACHING=true # Enable URL content caching
GOOGLE_GEMINI_URL_USER_AGENT=MCP-Gemini-Server/1.0 # Custom User-Agent
# Server Configuration
MCP_CLIENT_ID=gemini-sdk-client # Optional: Default client ID for MCP connections (defaults to "gemini-sdk-client")
MCP_TRANSPORT=stdio # Options: stdio, sse, streamable, http (replaced deprecated MCP_TRANSPORT_TYPE)
MCP_LOG_LEVEL=info # Optional: Log level for MCP operations (debug, info, warn, error)
MCP_ENABLE_STREAMING=true # Enable SSE streaming for HTTP transport
MCP_SESSION_TIMEOUT=3600 # Session timeout in seconds for HTTP transport
SESSION_STORE_TYPE=memory # Options: memory, sqlite
SQLITE_DB_PATH=./data/sessions.db # Path to SQLite database file. For production, consider an absolute path to a persistent volume.
ENABLE_HEALTH_CHECK=true
HEALTH_CHECK_PORT=3000
# GitHub Personal Access Token for API access (required for GitHub code review features)
# For public repos, token needs 'public_repo' and 'read:user' scopes
# For private repos, token needs 'repo' scope
GITHUB_API_TOKEN=your_github_token_here
```
--------------------------------------------------------------------------------
/tests/e2e/README.md:
--------------------------------------------------------------------------------
```markdown
# E2E Tests for MCP Gemini Server
## Overview
End-to-end tests should be separated from unit tests and run with real clients and servers. This directory contains examples and instructions for setting up E2E tests.
## Structure
```
tests/e2e/
├── README.md # This file
├── fixtures/ # Test data and configurations
├── clients/ # Test client implementations
│ └── mcp-test-client.ts # MCP protocol test client
└── scenarios/ # Test scenarios
├── basic-flow.test.ts # Basic initialization and tool calling
├── streaming.test.ts # Streaming response tests
└── session-management.test.ts # Session lifecycle tests
```
## Running E2E Tests
E2E tests should be run separately from unit tests:
```bash
# Run unit tests (fast, mocked)
npm run test
# Run E2E tests (slower, real servers)
npm run test:e2e
```
## Example E2E Test
```typescript
import { MCPTestClient } from './clients/mcp-test-client';
import { startServer } from './helpers/server-helper';
describe('E2E: Basic MCP Flow', () => {
let server: any;
let client: MCPTestClient;
beforeAll(async () => {
// Start real server
server = await startServer({
transport: 'streamable',
port: 3001
});
// Create real client
client = new MCPTestClient({
url: 'http://localhost:3001/mcp'
});
});
afterAll(async () => {
await client.disconnect();
await server.stop();
});
it('should complete full MCP flow', async () => {
// 1. Initialize
const initResult = await client.initialize();
expect(initResult.protocolVersion).toBe('2024-11-05');
// 2. List tools
const tools = await client.listTools();
expect(tools).toContain(
expect.objectContaining({ name: 'gemini_generateContent' })
);
// 3. Call tool
const result = await client.callTool('gemini_generateContent', {
prompt: 'Hello',
modelName: 'gemini-1.5-flash'
});
expect(result).toBeDefined();
});
});
```
## Test Client Implementation
The test client should implement the full MCP protocol:
```typescript
export class MCPTestClient {
private sessionId?: string;
private url: string;
constructor(options: { url: string }) {
this.url = options.url;
}
async initialize(): Promise<any> {
const response = await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/event-stream'
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {
protocolVersion: '2024-11-05',
capabilities: {},
clientInfo: {
name: 'test-client',
version: '1.0.0'
}
}
})
});
this.sessionId = response.headers.get('Mcp-Session-Id') || undefined;
return this.parseResponse(response);
}
// ... other methods
}
```
## Benefits of E2E Testing
1. **Real Protocol Testing**: Tests actual MCP protocol implementation
2. **Integration Verification**: Ensures all components work together
3. **Performance Testing**: Can measure real-world performance
4. **Regression Prevention**: Catches issues unit tests might miss
## Current Status
E2E tests are not yet implemented. When implementing:
1. Use a real MCP client library if available
2. Test against multiple transport types
3. Include error scenarios and edge cases
4. Add performance benchmarks
5. Consider using Docker for isolated test environments
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
[](https://mseep.ai/app/bsmi021-mcp-gemini-server)
# MCP Gemini Server
## Table of Contents
- [Overview](#overview)
- [File Uploads vs URL-Based Analysis](#file-uploads-vs-url-based-analysis)
- [Features](#features)
- [Prerequisites](#prerequisites)
- [Installation & Setup](#installation--setup)
- [Configuration](#configuration)
- [Available Tools](#available-tools)
- [Usage Examples](#usage-examples)
- [Supported Multimedia Analysis Use Cases](#supported-multimedia-analysis-use-cases)
- [MCP Gemini Server and Gemini SDK's MCP Function Calling](#mcp-gemini-server-and-gemini-sdks-mcp-function-calling)
- [Environment Variables](#environment-variables)
- [Security Considerations](#security-considerations)
- [Error Handling](#error-handling)
- [Development and Testing](#development-and-testing)
- [Contributing](#contributing)
- [Code Review Tools](#code-review-tools)
- [Server Features](#server-features)
- [Known Issues](#known-issues)
## Overview
This project provides a dedicated MCP (Model Context Protocol) server that wraps the `@google/genai` SDK (v0.10.0). It exposes Google's Gemini model capabilities as standard MCP tools, allowing other LLMs (like Claude) or MCP-compatible systems to leverage Gemini's features as a backend workhorse.
This server aims to simplify integration with Gemini models by providing a consistent, tool-based interface managed via the MCP standard. It supports the latest Gemini models including `gemini-1.5-pro-latest`, `gemini-1.5-flash`, and `gemini-2.5-pro` models.
**Important Note:** This server does not support direct file uploads. Instead, it focuses on URL-based multimedia analysis for images and videos. For text-based content processing, use the standard content generation tools.
## File Uploads vs URL-Based Analysis
### ❌ Not Supported: Direct File Uploads
This MCP Gemini Server **does not support** the following file upload operations:
- **Local file uploads**: Cannot upload files from your local filesystem to Gemini
- **Base64 encoded files**: Cannot process base64-encoded image or video data
- **Binary file data**: Cannot handle raw file bytes or binary data
- **File references**: Cannot process file IDs or references from uploaded content
- **Audio file uploads**: Cannot upload and transcribe audio files directly
**Why File Uploads Are Not Supported:**
- Simplified architecture focused on URL-based processing
- Enhanced security by avoiding file handling complexities
- Reduced storage and bandwidth requirements
- Streamlined codebase maintenance
### ✅ Fully Supported: URL-Based Multimedia Analysis
This server **fully supports** analyzing multimedia content from publicly accessible URLs:
**Image Analysis from URLs:**
- **Public image URLs**: Analyze images hosted on any publicly accessible web server
- **Supported formats**: PNG, JPEG, WebP, HEIC, HEIF via direct URL access
- **Multiple images**: Process multiple image URLs in a single request
- **Security validation**: Automatic URL validation and security screening
**YouTube Video Analysis:**
- **Public YouTube videos**: Full analysis of any public YouTube video content
- **Video understanding**: Extract insights, summaries, and detailed analysis
- **Educational content**: Perfect for analyzing tutorials, lectures, and educational videos
- **Multiple videos**: Process multiple YouTube URLs (up to 10 per request with Gemini 2.5+)
**Web Content Processing:**
- **HTML content**: Analyze and extract information from web pages
- **Mixed media**: Combine text content with embedded images and videos
- **Contextual analysis**: Process URLs alongside text prompts for comprehensive analysis
### Alternatives for Local Content
**If you have local files to analyze:**
1. **Host on a web server**: Upload your files to a public web server and use the URL
2. **Use cloud storage**: Upload to services like Google Drive, Dropbox, or AWS S3 with public access
3. **Use GitHub**: Host images in a GitHub repository and use the raw file URLs
4. **Use image hosting services**: Upload to services like Imgur, ImageBB, or similar platforms
**For audio content:**
- Use external transcription services (Whisper API, Google Speech-to-Text, etc.)
- Upload audio to YouTube and analyze the resulting video URL
- Use other MCP servers that specialize in audio processing
## Features
* **Core Generation:** Standard (`gemini_generateContent`) and streaming (`gemini_generateContentStream`) text generation with support for system instructions and cached content.
* **Function Calling:** Enables Gemini models to request the execution of client-defined functions (`gemini_functionCall`).
* **Stateful Chat:** Manages conversational context across multiple turns (`gemini_startChat`, `gemini_sendMessage`, `gemini_sendFunctionResult`) with support for system instructions, tools, and cached content.
* **URL-Based Multimedia Analysis:** Analyze images from public URLs and YouTube videos without file uploads. Direct file uploads are not supported.
* **Caching:** Create, list, retrieve, update, and delete cached content to optimize prompts with support for tools and tool configurations.
* **Image Generation:** Generate images from text prompts using Gemini 2.0 Flash Experimental (`gemini_generateImage`) with control over resolution, number of images, and negative prompts. Also supports the latest Imagen 3.1 model for high-quality dedicated image generation with advanced style controls. Note that Gemini 2.5 models (Flash and Pro) do not currently support image generation.
* **URL Context Processing:** Fetch and analyze web content directly from URLs with advanced security, caching, and content processing capabilities.
* `gemini_generateContent`: Enhanced with URL context support for including web content in prompts
* `gemini_generateContentStream`: Streaming generation with URL context integration
* `gemini_url_analysis`: Specialized tool for advanced URL content analysis with multiple analysis types
* **MCP Client:** Connect to and interact with external MCP servers.
* `mcpConnectToServer`: Establishes a connection to an external MCP server.
* `mcpListServerTools`: Lists available tools on a connected MCP server.
* `mcpCallServerTool`: Calls a function on a connected MCP server, with an option for file output.
* `mcpDisconnectFromServer`: Disconnects from an external MCP server.
* `writeToFile`: Writes content directly to files within allowed directories.
## Prerequisites
* Node.js (v18 or later)
* An API Key from **Google AI Studio** (<https://aistudio.google.com/app/apikey>).
* **Important:** The Caching API is **only compatible with Google AI Studio API keys** and is **not supported** when using Vertex AI credentials. This server does not currently support Vertex AI authentication.
## Installation & Setup
### Installing Manually
1. **Clone/Place Project:** Ensure the `mcp-gemini-server` project directory is accessible on your system.
2. **Install Dependencies:** Navigate to the project directory in your terminal and run:
```bash
npm install
```
3. **Build Project:** Compile the TypeScript source code:
```bash
npm run build
```
This command uses the TypeScript compiler (`tsc`) and outputs the JavaScript files to the `./dist` directory (as specified by `outDir` in `tsconfig.json`). The main server entry point will be `dist/server.js`.
4. **Generate Connection Token:** Create a strong, unique connection token for secure communication between your MCP client and the server. This is a shared secret that you generate and configure on both the server and client sides.
**Generate a secure token using one of these methods:**
**Option A: Using Node.js crypto (Recommended)**
```bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
**Option B: Using OpenSSL**
```bash
openssl rand -hex 32
```
**Option C: Using PowerShell (Windows)**
```powershell
[System.Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))
```
**Option D: Online Generator (Use with caution)**
Use a reputable password generator like [1Password](https://1password.com/password-generator/) or [Bitwarden](https://bitwarden.com/password-generator/) to generate a 64-character random string.
**Important Security Notes:**
- The token should be at least 32 characters long and contain random characters
- Never share this token or commit it to version control
- Use a different token for each server instance
- Store the token securely (environment variables, secrets manager, etc.)
- Save this token - you'll need to use the exact same value in both server and client configurations
5. **Configure MCP Client:** Add the server configuration to your MCP client's settings file (e.g., `cline_mcp_settings.json` for Cline/VSCode, or `claude_desktop_config.json` for Claude Desktop App). Replace `/path/to/mcp-gemini-server` with the actual **absolute path** on your system, `YOUR_API_KEY` with your Google AI Studio key, and `YOUR_GENERATED_CONNECTION_TOKEN` with the token you generated in step 4.
```json
{
"mcpServers": {
"gemini-server": { // Or your preferred name
"command": "node",
"args": ["/path/to/mcp-gemini-server/dist/server.js"], // Absolute path to the compiled server entry point
"env": {
"GOOGLE_GEMINI_API_KEY": "YOUR_API_KEY",
"MCP_SERVER_HOST": "localhost", // Required: Server host
"MCP_SERVER_PORT": "8080", // Required: Server port
"MCP_CONNECTION_TOKEN": "YOUR_GENERATED_CONNECTION_TOKEN", // Required: Use the token from step 4
"GOOGLE_GEMINI_MODEL": "gemini-1.5-flash", // Optional: Set a default model
// Optional security configurations removed - file operations no longer supported
"ALLOWED_OUTPUT_PATHS": "/var/opt/mcp-gemini-server/outputs,/tmp/mcp-gemini-outputs" // Optional: Comma-separated list of allowed output directories for mcpCallServerTool and writeToFileTool
},
"disabled": false,
"autoApprove": []
}
// ... other servers
}
}
```
**Important Notes:**
- The path in `args` must be the **absolute path** to the compiled `dist/server.js` file
- `MCP_SERVER_HOST`, `MCP_SERVER_PORT`, and `MCP_CONNECTION_TOKEN` are required unless `NODE_ENV` is set to `test`
- `MCP_CONNECTION_TOKEN` must be the exact same value you generated in step 4
- Ensure the path exists and the server has been built using `npm run build`
6. **Restart MCP Client:** Restart your MCP client application (e.g., VS Code with Cline extension, Claude Desktop App) to load the new server configuration. The MCP client will manage starting and stopping the server process.
## Configuration
The server uses environment variables for configuration, passed via the `env` object in the MCP settings:
* `GOOGLE_GEMINI_API_KEY` (**Required**): Your API key obtained from Google AI Studio.
* `GOOGLE_GEMINI_MODEL` (*Optional*): Specifies a default Gemini model name (e.g., `gemini-1.5-flash`, `gemini-1.0-pro`). If set, tools that require a model name (like `gemini_generateContent`, `gemini_startChat`, etc.) will use this default when the `modelName` parameter is omitted in the tool call. This simplifies client calls when primarily using one model. If this environment variable is *not* set, the `modelName` parameter becomes required for those tools. See the [Google AI documentation](https://ai.google.dev/models/gemini) for available model names.
* `ALLOWED_OUTPUT_PATHS` (*Optional*): A comma-separated list of absolute paths to directories where the `mcpCallServerTool` (with `outputToFile` parameter) and `writeToFileTool` are allowed to write files. If not set, file output will be disabled for these tools. This is a security measure to prevent arbitrary file writes.
## Available Tools
This server provides the following MCP tools. Parameter schemas are defined using Zod for validation and description.
**Validation and Error Handling:** All parameters are validated using Zod schemas at both the MCP tool level and service layer, providing consistent validation, detailed error messages, and type safety. The server implements comprehensive error mapping to provide clear, actionable error messages.
**Retry Logic:** API requests automatically use exponential backoff retry for transient errors (network issues, rate limits, timeouts), improving reliability for unstable connections. The retry mechanism includes configurable parameters for maximum attempts, delay times, and jitter to prevent thundering herd effects.
**Note on Optional Parameters:** Many tools accept complex optional parameters (e.g., `generationConfig`, `safetySettings`, `toolConfig`, `history`, `functionDeclarations`, `contents`). These parameters are typically objects or arrays whose structure mirrors the types defined in the underlying `@google/genai` SDK (v0.10.0). For the exact structure and available fields within these complex parameters, please refer to:
1. The corresponding `src/tools/*Params.ts` file in this project.
2. The official [Google AI JS SDK Documentation](https://github.com/google/generative-ai-js).
### Core Generation
* **`gemini_generateContent`**
* *Description:* Generates non-streaming text content from a prompt with optional URL context support.
* *Required Params:* `prompt` (string)
* *Optional Params:*
* `modelName` (string) - Name of the model to use
* `generationConfig` (object) - Controls generation parameters like temperature, topP, etc.
* `thinkingConfig` (object) - Controls model reasoning process
* `thinkingBudget` (number) - Maximum tokens for reasoning (0-24576)
* `reasoningEffort` (string) - Simplified control: "none" (0 tokens), "low" (1K), "medium" (8K), "high" (24K)
* `safetySettings` (array) - Controls content filtering by harm category
* `systemInstruction` (string or object) - System instruction to guide model behavior
* `cachedContentName` (string) - Identifier for cached content to use with this request
* `urlContext` (object) - Fetch and include web content from URLs
* `urls` (array) - URLs to fetch and include as context (max 20)
* `fetchOptions` (object) - Configuration for URL fetching
* `maxContentKb` (number) - Maximum content size per URL in KB (default: 100)
* `timeoutMs` (number) - Fetch timeout per URL in milliseconds (default: 10000)
* `includeMetadata` (boolean) - Include URL metadata in context (default: true)
* `convertToMarkdown` (boolean) - Convert HTML to markdown (default: true)
* `allowedDomains` (array) - Specific domains to allow for this request
* `userAgent` (string) - Custom User-Agent header for URL requests
* `modelPreferences` (object) - Model selection preferences
* *Note:* Can handle multimodal inputs, cached content, and URL context for comprehensive content generation
* *Thinking Budget:* Controls the token budget for model reasoning. Lower values provide faster responses, higher values improve complex reasoning.
* **`gemini_generateContentStream`**
* *Description:* Generates text content via streaming using Server-Sent Events (SSE) for real-time content delivery with URL context support.
* *Required Params:* `prompt` (string)
* *Optional Params:*
* `modelName` (string) - Name of the model to use
* `generationConfig` (object) - Controls generation parameters like temperature, topP, etc.
* `thinkingConfig` (object) - Controls model reasoning process
* `thinkingBudget` (number) - Maximum tokens for reasoning (0-24576)
* `reasoningEffort` (string) - Simplified control: "none" (0 tokens), "low" (1K), "medium" (8K), "high" (24K)
* `safetySettings` (array) - Controls content filtering by harm category
* `systemInstruction` (string or object) - System instruction to guide model behavior
* `cachedContentName` (string) - Identifier for cached content to use with this request
* `urlContext` (object) - Same URL context options as `gemini_generateContent`
* `modelPreferences` (object) - Model selection preferences
### Function Calling
* **`gemini_functionCall`**
* *Description:* Sends a prompt and function declarations to the model, returning either a text response or a requested function call object (as a JSON string).
* *Required Params:* `prompt` (string), `functionDeclarations` (array)
* *Optional Params:*
* `modelName` (string) - Name of the model to use
* `generationConfig` (object) - Controls generation parameters
* `safetySettings` (array) - Controls content filtering
* `toolConfig` (object) - Configures tool behavior like temperature and confidence thresholds
### Stateful Chat
* **`gemini_startChat`**
* *Description:* Initiates a new stateful chat session and returns a unique `sessionId`.
* *Optional Params:*
* `modelName` (string) - Name of the model to use
* `history` (array) - Initial conversation history
* `tools` (array) - Tool definitions including function declarations
* `generationConfig` (object) - Controls generation parameters
* `thinkingConfig` (object) - Controls model reasoning process
* `thinkingBudget` (number) - Maximum tokens for reasoning (0-24576)
* `reasoningEffort` (string) - Simplified control: "none" (0 tokens), "low" (1K), "medium" (8K), "high" (24K)
* `safetySettings` (array) - Controls content filtering
* `systemInstruction` (string or object) - System instruction to guide model behavior
* `cachedContentName` (string) - Identifier for cached content to use with this session
* **`gemini_sendMessage`**
* *Description:* Sends a message within an existing chat session.
* *Required Params:* `sessionId` (string), `message` (string)
* *Optional Params:*
* `generationConfig` (object) - Controls generation parameters
* `thinkingConfig` (object) - Controls model reasoning process
* `thinkingBudget` (number) - Maximum tokens for reasoning (0-24576)
* `reasoningEffort` (string) - Simplified control: "none" (0 tokens), "low" (1K), "medium" (8K), "high" (24K)
* `safetySettings` (array) - Controls content filtering
* `tools` (array) - Tool definitions including function declarations
* `toolConfig` (object) - Configures tool behavior
* `cachedContentName` (string) - Identifier for cached content to use with this message
* **`gemini_sendFunctionResult`**
* *Description:* Sends the result of a function execution back to a chat session.
* *Required Params:* `sessionId` (string), `functionResponse` (string) - The result of the function execution
* *Optional Params:* `functionCall` (object) - Reference to the original function call
* **`gemini_routeMessage`**
* *Description:* Routes a message to the most appropriate model from a provided list based on message content. Returns both the model's response and which model was selected.
* *Required Params:*
* `message` (string) - The text message to be routed to the most appropriate model
* `models` (array) - Array of model names to consider for routing (e.g., ['gemini-1.5-flash', 'gemini-1.5-pro']). The first model in the list will be used for routing decisions.
* *Optional Params:*
* `routingPrompt` (string) - Custom prompt to use for routing decisions. If not provided, a default routing prompt will be used.
* `defaultModel` (string) - Model to fall back to if routing fails. If not provided and routing fails, an error will be thrown.
* `generationConfig` (object) - Generation configuration settings to apply to the selected model's response.
* `thinkingConfig` (object) - Controls model reasoning process
* `thinkingBudget` (number) - Maximum tokens for reasoning (0-24576)
* `reasoningEffort` (string) - Simplified control: "none" (0 tokens), "low" (1K), "medium" (8K), "high" (24K)
* `safetySettings` (array) - Safety settings to apply to both routing and final response.
* `systemInstruction` (string or object) - A system instruction to guide the model's behavior after routing.
### Remote File Operations (Removed)
**Note:** Direct file upload operations are no longer supported by this server. The server now focuses exclusively on URL-based multimedia analysis for images and videos, and text-based content generation.
**Alternative Approaches:**
- **For Image Analysis:** Use publicly accessible image URLs with `gemini_generateContent` or `gemini_url_analysis` tools
- **For Video Analysis:** Use publicly accessible YouTube video URLs for content analysis
- **For Audio Content:** Audio transcription via file uploads is not supported - consider using URL-based services that provide audio transcripts
- **For Document Analysis:** Use URL-based document analysis or convert documents to publicly accessible formats
### Caching (Google AI Studio Key Required)
* **`gemini_createCache`**
* *Description:* Creates cached content for compatible models (e.g., `gemini-1.5-flash`).
* *Required Params:* `contents` (array), `model` (string)
* *Optional Params:*
* `displayName` (string) - Human-readable name for the cached content
* `systemInstruction` (string or object) - System instruction to apply to the cached content
* `ttl` (string - e.g., '3600s') - Time-to-live for the cached content
* `tools` (array) - Tool definitions for use with the cached content
* `toolConfig` (object) - Configuration for the tools
* **`gemini_listCaches`**
* *Description:* Lists existing cached content.
* *Required Params:* None
* *Optional Params:* `pageSize` (number), `pageToken` (string - Note: `pageToken` may not be reliably returned currently).
* **`gemini_getCache`**
* *Description:* Retrieves metadata for specific cached content.
* *Required Params:* `cacheName` (string - e.g., `cachedContents/abc123xyz`)
* **`gemini_updateCache`**
* *Description:* Updates metadata and contents for cached content.
* *Required Params:* `cacheName` (string), `contents` (array)
* *Optional Params:*
* `displayName` (string) - Updated display name
* `systemInstruction` (string or object) - Updated system instruction
* `ttl` (string) - Updated time-to-live
* `tools` (array) - Updated tool definitions
* `toolConfig` (object) - Updated tool configuration
* **`gemini_deleteCache`**
* *Description:* Deletes cached content.
* *Required Params:* `cacheName` (string - e.g., `cachedContents/abc123xyz`)
### Image Generation
* **`gemini_generateImage`**
* *Description:* Generates images from text prompts using available image generation models.
* *Required Params:* `prompt` (string - descriptive text prompt for image generation)
* *Optional Params:*
* `modelName` (string - defaults to "imagen-3.1-generate-003" for high-quality dedicated image generation or use "gemini-2.0-flash-exp-image-generation" for Gemini models)
* `resolution` (string enum: "512x512", "1024x1024", "1536x1536")
* `numberOfImages` (number - 1-8, default: 1)
* `safetySettings` (array) - Controls content filtering for generated images
* `negativePrompt` (string - features to avoid in the generated image)
* `stylePreset` (string enum: "photographic", "digital-art", "cinematic", "anime", "3d-render", "oil-painting", "watercolor", "pixel-art", "sketch", "comic-book", "neon", "fantasy")
* `seed` (number - integer value for reproducible generation)
* `styleStrength` (number - strength of style preset, 0.0-1.0)
* *Response:* Returns an array of base64-encoded images with metadata including dimensions and MIME type.
* *Notes:* Image generation uses significant resources, especially at higher resolutions. Consider using smaller resolutions for faster responses and less resource usage.
### Audio Transcription (Removed)
**Note:** Audio transcription via direct file uploads is no longer supported by this server. The server focuses on URL-based multimedia analysis for images and videos.
**Alternative Approaches for Audio Content:**
- **YouTube Videos:** Use the YouTube video analysis capabilities to analyze video content that includes audio
- **External Services:** Use dedicated audio transcription services and analyze their output as text content
- **URL-Based Audio:** If audio content is available via public URLs in supported formats, consider using external transcription services first, then analyze the resulting text
### URL Content Analysis
* **`gemini_url_analysis`**
* *Description:* Advanced URL analysis tool that fetches content from web pages and performs specialized analysis tasks with comprehensive security and performance optimizations.
* *Required Params:*
* `urls` (array) - URLs to analyze (1-20 URLs supported)
* `analysisType` (string enum) - Type of analysis to perform:
* `summary` - Comprehensive content summarization
* `comparison` - Multi-URL content comparison
* `extraction` - Structured information extraction
* `qa` - Question-based content analysis
* `sentiment` - Emotional tone analysis
* `fact-check` - Credibility assessment
* `content-classification` - Topic and type categorization
* `readability` - Accessibility and complexity analysis
* `seo-analysis` - Search optimization evaluation
* *Optional Params:*
* `query` (string) - Specific query or instruction for the analysis
* `extractionSchema` (object) - JSON schema for structured data extraction
* `questions` (array) - List of specific questions to answer (for Q&A analysis)
* `compareBy` (array) - Specific aspects to compare when using comparison analysis
* `outputFormat` (string enum: "text", "json", "markdown", "structured") - Desired output format
* `includeMetadata` (boolean) - Include URL metadata in the analysis (default: true)
* `fetchOptions` (object) - Advanced URL fetching options (same as urlContext fetchOptions)
* `modelName` (string) - Specific Gemini model to use (auto-selected if not specified)
* *Security Features:* Multi-layer URL validation, domain restrictions, private network protection, and rate limiting
* *Performance Features:* Intelligent caching, concurrent processing, and optimal model selection based on content complexity
### MCP Client Tools
* **`mcpConnectToServer`**
* *Description:* Establishes a connection to an external MCP server and returns a connection ID.
* *Required Params:*
* `serverId` (string): A unique identifier for this server connection.
* `connectionType` (string enum: "sse" | "stdio"): The transport protocol to use.
* `sseUrl` (string, optional if `connectionType` is "stdio"): The URL for SSE connection.
* `stdioCommand` (string, optional if `connectionType` is "sse"): The command to run for stdio connection.
* `stdioArgs` (array of strings, optional): Arguments for the stdio command.
* `stdioEnv` (object, optional): Environment variables for the stdio command.
* *Important:* This tool returns a `connectionId` that must be used in subsequent calls to `mcpListServerTools`, `mcpCallServerTool`, and `mcpDisconnectFromServer`. This `connectionId` is generated internally and is different from the `serverId` parameter.
* **`mcpListServerTools`**
* *Description:* Lists available tools on a connected MCP server.
* *Required Params:*
* `connectionId` (string): The connection identifier returned by `mcpConnectToServer`.
* **`mcpCallServerTool`**
* *Description:* Calls a function on a connected MCP server.
* *Required Params:*
* `connectionId` (string): The connection identifier returned by `mcpConnectToServer`.
* `toolName` (string): The name of the tool to call on the remote server.
* `toolArgs` (object): The arguments to pass to the remote tool.
* *Optional Params:*
* `outputToFile` (string): If provided, the tool's output will be written to this file path. The path must be within one of the directories specified in the `ALLOWED_OUTPUT_PATHS` environment variable.
* **`mcpDisconnectFromServer`**
* *Description:* Disconnects from an external MCP server.
* *Required Params:*
* `connectionId` (string): The connection identifier returned by `mcpConnectToServer`.
* **`writeToFile`**
* *Description:* Writes content directly to a file.
* *Required Params:*
* `filePath` (string): The absolute path of the file to write to. Must be within one of the directories specified in the `ALLOWED_OUTPUT_PATHS` environment variable.
* `content` (string): The content to write to the file.
* *Optional Params:*
* `overwrite` (boolean, default: false): If true, overwrite the file if it already exists. Otherwise, an error will be thrown if the file exists.
## Usage Examples
Here are examples of how an MCP client (like Claude) might call these tools using the `use_mcp_tool` format:
**Example 1: Simple Content Generation (Using Default Model)**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Write a short poem about a rubber duck."
}
</arguments>
</use_mcp_tool>
```
**Example 2: Content Generation (Specifying Model & Config)**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"modelName": "gemini-1.0-pro",
"prompt": "Explain the concept of recursion in programming.",
"generationConfig": {
"temperature": 0.7,
"maxOutputTokens": 500
}
}
</arguments>
</use_mcp_tool>
```
**Example 2b: Content Generation with Thinking Budget Control**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"modelName": "gemini-1.5-pro",
"prompt": "Solve this complex math problem: Find all values of x where 2sin(x) = x^2-x+1 in the range [0, 2π].",
"generationConfig": {
"temperature": 0.2,
"maxOutputTokens": 1000,
"thinkingConfig": {
"thinkingBudget": 8192
}
}
}
</arguments>
</use_mcp_tool>
```
**Example 2c: Content Generation with Simplified Reasoning Effort**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"modelName": "gemini-1.5-pro",
"prompt": "Solve this complex math problem: Find all values of x where 2sin(x) = x^2-x+1 in the range [0, 2π].",
"generationConfig": {
"temperature": 0.2,
"maxOutputTokens": 1000,
"thinkingConfig": {
"reasoningEffort": "high"
}
}
}
</arguments>
</use_mcp_tool>
```
**Example 3: Starting and Continuing a Chat**
*Start Chat:*
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_startChat</tool_name>
<arguments>
{}
</arguments>
</use_mcp_tool>
```
*(Assume response contains `sessionId: "some-uuid-123"`)*
*Send Message:*
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_sendMessage</tool_name>
<arguments>
{
"sessionId": "some-uuid-123",
"message": "Hello! Can you tell me about the Gemini API?"
}
</arguments>
</use_mcp_tool>
```
**Example 4: Content Generation with System Instructions (Simplified Format)**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"modelName": "gemini-2.5-pro-exp",
"prompt": "What should I do with my day off?",
"systemInstruction": "You are a helpful assistant that provides friendly and detailed advice. You should focus on outdoor activities and wellness."
}
</arguments>
</use_mcp_tool>
```
**Example 5: Content Generation with System Instructions (Object Format)**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"modelName": "gemini-1.5-pro-latest",
"prompt": "What should I do with my day off?",
"systemInstruction": {
"parts": [
{
"text": "You are a helpful assistant that provides friendly and detailed advice. You should focus on outdoor activities and wellness."
}
]
}
}
</arguments>
</use_mcp_tool>
```
**Example 6: Using Cached Content with System Instruction**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"modelName": "gemini-2.5-pro-exp",
"prompt": "Explain how these concepts relate to my product?",
"cachedContentName": "cachedContents/abc123xyz",
"systemInstruction": "You are a product expert who explains technical concepts in simple terms."
}
</arguments>
</use_mcp_tool>
```
**Example 6: Generating an Image**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateImage</tool_name>
<arguments>
{
"prompt": "A futuristic cityscape with flying cars and neon lights",
"modelName": "gemini-2.0-flash-exp-image-generation",
"resolution": "1024x1024",
"numberOfImages": 1,
"negativePrompt": "dystopian, ruins, dark, gloomy"
}
</arguments>
</use_mcp_tool>
```
**Example 6b: Generating a High-Quality Image with Imagen 3.1**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateImage</tool_name>
<arguments>
{
"prompt": "A futuristic cityscape with flying cars and neon lights",
"modelName": "imagen-3.1-generate-003",
"resolution": "1024x1024",
"numberOfImages": 4,
"negativePrompt": "dystopian, ruins, dark, gloomy"
}
</arguments>
</use_mcp_tool>
```
**Example 6c: Using Advanced Style Options**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateImage</tool_name>
<arguments>
{
"prompt": "A futuristic cityscape with flying cars and neon lights",
"modelName": "imagen-3.1-generate-003",
"resolution": "1024x1024",
"numberOfImages": 2,
"stylePreset": "anime",
"styleStrength": 0.8,
"seed": 12345
}
</arguments>
</use_mcp_tool>
```
**Example 7: Message Routing Between Models**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_routeMessage</tool_name>
<arguments>
{
"message": "Can you create a detailed business plan for a sustainable fashion startup?",
"models": ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.5-pro"],
"routingPrompt": "Analyze this message and determine which model would be best suited to handle it. Consider: gemini-1.5-flash for simpler tasks, gemini-1.5-pro for balanced capabilities, and gemini-2.5-pro for complex creative tasks.",
"defaultModel": "gemini-1.5-pro",
"generationConfig": {
"temperature": 0.7,
"maxOutputTokens": 1024
}
}
</arguments>
</use_mcp_tool>
```
The response will be a JSON string containing both the text response and which model was chosen:
```json
{
"text": "# Business Plan for Sustainable Fashion Startup\n\n## Executive Summary\n...",
"chosenModel": "gemini-2.5-pro"
}
```
**Example 8: Using URL Context with Content Generation**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Summarize the main points from these articles and compare their approaches to sustainable technology",
"urlContext": {
"urls": [
"https://example.com/sustainable-tech-2024",
"https://techblog.com/green-innovation"
],
"fetchOptions": {
"maxContentKb": 150,
"includeMetadata": true,
"convertToMarkdown": true
}
},
"modelPreferences": {
"preferQuality": true,
"taskType": "reasoning"
}
}
</arguments>
</use_mcp_tool>
```
**Example 9: Advanced URL Analysis**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_url_analysis</tool_name>
<arguments>
{
"urls": ["https://company.com/about", "https://company.com/products"],
"analysisType": "extraction",
"extractionSchema": {
"companyName": "string",
"foundedYear": "number",
"numberOfEmployees": "string",
"mainProducts": "array",
"headquarters": "string",
"financialInfo": "object"
},
"outputFormat": "json",
"query": "Extract comprehensive company information including business details and product offerings"
}
</arguments>
</use_mcp_tool>
```
**Example 10: Multi-URL Content Comparison**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_url_analysis</tool_name>
<arguments>
{
"urls": [
"https://site1.com/pricing",
"https://site2.com/pricing",
"https://site3.com/pricing"
],
"analysisType": "comparison",
"compareBy": ["pricing models", "features", "target audience", "value proposition"],
"outputFormat": "markdown",
"includeMetadata": true
}
</arguments>
</use_mcp_tool>
```
**Example 11: URL Content with Security Restrictions**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Analyze the content from these trusted news sources",
"urlContext": {
"urls": [
"https://reuters.com/article/tech-news",
"https://bbc.com/news/technology"
],
"fetchOptions": {
"allowedDomains": ["reuters.com", "bbc.com"],
"maxContentKb": 200,
"timeoutMs": 15000,
"userAgent": "Research-Bot/1.0"
}
}
}
</arguments>
</use_mcp_tool>
```
### URL-Based Image Analysis Examples
These examples demonstrate how to analyze images from public URLs using Gemini's native image understanding capabilities. The server processes images by fetching them from URLs and converting them to the format required by the Gemini API. Note that this server does not support direct file uploads - all image analysis must be performed using publicly accessible image URLs.
**Example 17: Basic Image Description and Analysis**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Please describe this image in detail, including objects, people, colors, setting, and any text you can see.",
"urlContext": {
"urls": ["https://example.com/images/photo.jpg"],
"fetchOptions": {
"includeMetadata": true,
"timeoutMs": 15000
}
}
}
</arguments>
</use_mcp_tool>
```
**Example 18: Object Detection and Identification**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Identify and list all objects visible in this image. For each object, describe its location, size relative to other objects, and any notable characteristics.",
"urlContext": {
"urls": ["https://example.com/images/scene.png"],
"fetchOptions": {
"includeMetadata": false,
"timeoutMs": 20000
}
}
}
</arguments>
</use_mcp_tool>
```
**Example 19: Chart and Data Visualization Analysis**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Analyze this chart or graph. What type of visualization is it? What are the main data points, trends, and insights? Extract any numerical values, labels, and time periods shown.",
"urlContext": {
"urls": ["https://example.com/charts/sales-data.png"]
},
"modelPreferences": {
"preferQuality": true,
"taskType": "reasoning"
}
}
</arguments>
</use_mcp_tool>
```
**Example 20: Comparative Image Analysis**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Compare these two images side by side. Describe the differences and similarities in terms of objects, composition, colors, style, and any other notable aspects.",
"urlContext": {
"urls": [
"https://example.com/before-renovation.jpg",
"https://example.com/after-renovation.jpg"
],
"fetchOptions": {
"maxContentKb": 200,
"includeMetadata": true
}
}
}
</arguments>
</use_mcp_tool>
```
**Example 21: Text Extraction from Images (OCR)**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Extract all text visible in this image. Include any signs, labels, captions, or written content. Maintain the original formatting and structure as much as possible.",
"urlContext": {
"urls": ["https://example.com/documents/screenshot.png"]
}
}
</arguments>
</use_mcp_tool>
```
**Example 22: Technical Diagram or Flowchart Analysis**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Analyze this technical diagram, flowchart, or schematic. Explain the system architecture, identify components, describe the relationships and data flow, and interpret any symbols or notations used.",
"urlContext": {
"urls": ["https://docs.example.com/architecture-diagram.png"],
"fetchOptions": {
"maxContentKb": 100,
"includeMetadata": true
}
},
"modelPreferences": {
"preferQuality": true,
"taskType": "reasoning"
}
}
</arguments>
</use_mcp_tool>
```
**Example 23: Image Analysis with Specific Questions**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Looking at this image, please answer these specific questions: 1) What is the main subject? 2) What colors dominate the scene? 3) Are there any people visible? 4) What appears to be the setting or location? 5) What mood or atmosphere does the image convey?",
"urlContext": {
"urls": ["https://example.com/images/landscape.jpg"]
}
}
</arguments>
</use_mcp_tool>
```
**Example 24: Image Analysis with Security Restrictions**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Analyze the composition and design elements in this image, focusing on visual hierarchy, layout principles, and aesthetic choices.",
"urlContext": {
"urls": ["https://trusted-cdn.example.com/design-mockup.jpg"],
"fetchOptions": {
"allowedDomains": ["trusted-cdn.example.com", "assets.example.com"],
"maxContentKb": 150,
"timeoutMs": 25000,
"includeMetadata": false
}
}
}
</arguments>
</use_mcp_tool>
```
**Important Notes for URL-Based Image Analysis:**
- **Supported formats**: PNG, JPEG, WebP, HEIC, HEIF (as per Gemini API specifications)
- **Image access**: Images must be accessible via public URLs without authentication
- **Size considerations**: Large images are automatically processed in sections by Gemini
- **Processing**: The server fetches images from URLs and converts them to the format required by Gemini API
- **Security**: The server applies restrictions to prevent access to private networks or malicious domains
- **Performance**: Image analysis may take longer for high-resolution images due to processing complexity
- **Token usage**: Image dimensions affect token consumption - larger images use more tokens
### YouTube Video Analysis Examples
These examples demonstrate how to analyze YouTube videos using Gemini's video understanding capabilities. The server can process publicly accessible YouTube videos by providing their URLs. Note that only public YouTube videos are supported - private, unlisted, or region-restricted videos cannot be analyzed.
**Example 25: Basic YouTube Video Analysis**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Please analyze this YouTube video and provide a comprehensive summary including the main topics discussed, key points, and overall theme.",
"urlContext": {
"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
"fetchOptions": {
"includeMetadata": true,
"timeoutMs": 30000
}
}
}
</arguments>
</use_mcp_tool>
```
**Example 26: YouTube Video Content Extraction with Timestamps**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Analyze this educational YouTube video and create a detailed outline with key topics and approximate timestamps. Identify the main learning objectives and key concepts covered.",
"urlContext": {
"urls": ["https://www.youtube.com/watch?v=EXAMPLE_VIDEO_ID"],
"fetchOptions": {
"maxContentKb": 300,
"includeMetadata": true,
"timeoutMs": 45000
}
},
"modelPreferences": {
"preferQuality": true,
"taskType": "reasoning"
}
}
</arguments>
</use_mcp_tool>
```
**Example 27: YouTube Video Analysis with Specific Questions**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Watch this YouTube video and answer these specific questions: 1) What is the main message or thesis? 2) Who is the target audience? 3) What evidence or examples are provided? 4) What are the key takeaways? 5) How is the content structured?",
"urlContext": {
"urls": ["https://www.youtube.com/watch?v=EXAMPLE_VIDEO_ID"]
}
}
</arguments>
</use_mcp_tool>
```
**Example 28: Comparative Analysis of Multiple YouTube Videos**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Compare and contrast these YouTube videos. Analyze their different approaches to the topic, presentation styles, key arguments, and conclusions. Identify similarities and differences in their perspectives.",
"urlContext": {
"urls": [
"https://www.youtube.com/watch?v=VIDEO_ID_1",
"https://www.youtube.com/watch?v=VIDEO_ID_2"
],
"fetchOptions": {
"maxContentKb": 400,
"includeMetadata": true,
"timeoutMs": 60000
}
},
"modelPreferences": {
"preferQuality": true,
"taskType": "reasoning"
}
}
</arguments>
</use_mcp_tool>
```
**Example 29: YouTube Video Technical Analysis**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Analyze this technical YouTube tutorial and extract step-by-step instructions, identify required tools or materials, note any code examples or commands shown, and highlight important warnings or best practices mentioned.",
"urlContext": {
"urls": ["https://www.youtube.com/watch?v=TECH_TUTORIAL_ID"],
"fetchOptions": {
"includeMetadata": true,
"timeoutMs": 40000
}
}
}
</arguments>
</use_mcp_tool>
```
**Example 30: YouTube Video Sentiment and Style Analysis**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_url_analysis</tool_name>
<arguments>
{
"urls": ["https://www.youtube.com/watch?v=EXAMPLE_VIDEO_ID"],
"analysisType": "sentiment",
"outputFormat": "structured",
"query": "Analyze the tone, mood, and presentation style of this YouTube video. Assess the speaker's credibility, engagement level, and overall effectiveness of communication."
}
</arguments>
</use_mcp_tool>
```
**Example 31: YouTube Video Educational Content Assessment**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Evaluate this educational YouTube video for accuracy, clarity, and pedagogical effectiveness. Identify the teaching methods used, assess how well complex concepts are explained, and suggest improvements if any.",
"urlContext": {
"urls": ["https://www.youtube.com/watch?v=EDUCATIONAL_VIDEO_ID"],
"fetchOptions": {
"maxContentKb": 250,
"includeMetadata": true
}
},
"modelPreferences": {
"preferQuality": true,
"taskType": "reasoning"
}
}
</arguments>
</use_mcp_tool>
```
**Example 32: YouTube Video with Domain Security Restrictions**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>gemini_generateContent</tool_name>
<arguments>
{
"prompt": "Analyze the content and key messages of this YouTube video, focusing on factual accuracy and source credibility.",
"urlContext": {
"urls": ["https://www.youtube.com/watch?v=TRUSTED_VIDEO_ID"],
"fetchOptions": {
"allowedDomains": ["youtube.com", "www.youtube.com"],
"maxContentKb": 200,
"timeoutMs": 35000,
"includeMetadata": true
}
}
}
</arguments>
</use_mcp_tool>
```
**Important Notes for YouTube Video Analysis:**
- **Public videos only**: Only publicly accessible YouTube videos can be analyzed
- **URL format**: Use standard YouTube URLs (youtube.com/watch?v=VIDEO_ID or youtu.be/VIDEO_ID)
- **Processing time**: Video analysis typically takes longer than text or image analysis
- **Content limitations**: Very long videos may have content truncated or processed in segments
- **Metadata**: Video metadata (title, description, duration) is included when `includeMetadata: true`
- **Language support**: Gemini can analyze videos in multiple languages
- **Content restrictions**: The server applies the same security restrictions as other URL content
- **Token usage**: Video analysis can consume significant tokens depending on video length and complexity
## Supported Multimedia Analysis Use Cases
The MCP Gemini Server supports comprehensive multimedia analysis through URL-based processing, leveraging Google Gemini's advanced vision and video understanding capabilities. Below are the key use cases organized by content type:
### Image Analysis Use Cases
**Content Understanding:**
- **Product Analysis**: Analyze product images for features, design elements, and quality assessment
- **Document OCR**: Extract and transcribe text from images of documents, receipts, and forms
- **Chart & Graph Analysis**: Interpret data visualizations, extract key insights, and explain trends
- **Technical Diagrams**: Understand architectural diagrams, flowcharts, and technical schematics
- **Medical Images**: Analyze medical charts, X-rays, and diagnostic images (for educational purposes)
- **Art & Design**: Analyze artistic compositions, color schemes, and design principles
**Comparative Analysis:**
- **Before/After Comparisons**: Compare multiple images to identify changes and differences
- **Product Comparisons**: Analyze multiple product images for feature comparison
- **A/B Testing**: Evaluate design variations and visual differences
**Security & Quality:**
- **Content Moderation**: Identify inappropriate or harmful visual content
- **Quality Assessment**: Evaluate image quality, resolution, and technical aspects
- **Brand Compliance**: Check images for brand guideline adherence
### Video Analysis Use Cases
**Educational Content:**
- **Lecture Analysis**: Extract key concepts, create summaries, and identify important timestamps
- **Tutorial Understanding**: Break down step-by-step instructions and highlight key procedures
- **Training Materials**: Analyze corporate training videos and extract learning objectives
- **Academic Research**: Process research presentations and extract methodologies
**Content Creation:**
- **Video Summarization**: Generate concise summaries of long-form video content
- **Transcript Generation**: Create detailed transcripts with speaker identification
- **Content Categorization**: Classify videos by topic, genre, or content type
- **Sentiment Analysis**: Assess emotional tone and audience engagement indicators
**Technical Analysis:**
- **Software Demonstrations**: Extract software features and usage instructions
- **Product Reviews**: Analyze product demonstration videos and extract key insights
- **Troubleshooting Guides**: Parse technical support videos for problem-solving steps
- **Code Reviews**: Analyze programming tutorial videos and extract code examples
**Business Intelligence:**
- **Market Research**: Analyze promotional videos and marketing content
- **Competitive Analysis**: Study competitor video content and strategies
- **Customer Feedback**: Process video testimonials and feedback sessions
- **Event Coverage**: Analyze conference presentations and keynote speeches
### Integration Capabilities
**Multi-Modal Analysis:**
- Combine text prompts with image/video URLs for contextual analysis
- Process multiple media types in single requests for comprehensive insights
- Cross-reference visual content with textual instructions
**Workflow Integration:**
- Chain multiple analysis operations for complex workflows
- Export results to files for further processing
- Integrate with external MCP servers for extended functionality
**Security & Performance:**
- URL validation and security screening for safe content processing
- Caching support for frequently analyzed content
- Batch processing capabilities for multiple media items
**Example 12: Connecting to an External MCP Server (SSE)**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>mcpConnectToServer</tool_name>
<arguments>
{
"serverId": "my-external-server",
"connectionType": "sse",
"sseUrl": "http://localhost:8080/mcp"
}
</arguments>
</use_mcp_tool>
```
*(Assume response contains a unique connection ID like: `connectionId: "12345-abcde-67890"`)*
**Example 13: Calling a Tool on an External MCP Server and Writing Output to File**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>mcpCallServerTool</tool_name>
<arguments>
{
"connectionId": "12345-abcde-67890", // Use the connectionId returned by mcpConnectToServer
"toolName": "remote_tool_name",
"toolArgs": { "param1": "value1" },
"outputToFile": "/var/opt/mcp-gemini-server/outputs/result.json"
}
</arguments>
</use_mcp_tool>
```
**Important: The `connectionId` used in MCP client tools must be the connection identifier returned by `mcpConnectToServer`, not the original `serverId` parameter.**
**Note:** The `outputToFile` path must be within one of the directories specified in the `ALLOWED_OUTPUT_PATHS` environment variable. For example, if `ALLOWED_OUTPUT_PATHS="/path/to/allowed/output,/another/allowed/path"`, then the file path must be a subdirectory of one of these paths.
**Example 14: Writing Content Directly to a File**
```xml
<use_mcp_tool>
<server_name>gemini-server</server_name>
<tool_name>writeToFile</tool_name>
<arguments>
{
"filePath": "/path/to/allowed/output/my_notes.txt",
"content": "This is some important content.",
"overwrite": true
}
</arguments>
</use_mcp_tool>
```
**Note:** Like with `mcpCallServerTool`, the `filePath` must be within one of the directories specified in the `ALLOWED_OUTPUT_PATHS` environment variable. This is a critical security feature to prevent unauthorized file writes.
## `mcp-gemini-server` and Gemini SDK's MCP Function Calling
The official Google Gemini API documentation includes examples (such as for [function calling with MCP structure](https://ai.google.dev/gemini-api/docs/function-calling?example=weather#model_context_protocol_mcp)) that demonstrate how you can use the client-side Gemini SDK (e.g., in Python or Node.js) to interact with the Gemini API. In such scenarios, particularly for function calling, the client SDK itself can be used to structure requests and handle responses in a manner that aligns with MCP principles.
The `mcp-gemini-server` project offers a complementary approach by providing a **fully implemented, standalone MCP server**. Instead of your client application directly using the Gemini SDK to format MCP-style messages for the Gemini API, your client application (which could be another LLM like Claude, a custom script, or any MCP-compatible system) would:
1. Connect to an instance of this `mcp-gemini-server`.
2. Call the pre-defined MCP tools exposed by this server, such as `gemini_functionCall`, `gemini_generateContent`, etc.
This `mcp-gemini-server` then internally handles all the necessary interactions with the Google Gemini API, including structuring the requests, managing API keys, and processing responses, abstracting these details away from your MCP client.
### Benefits of using `mcp-gemini-server`:
* **Abstraction & Simplicity:** Client applications don't need to integrate the Gemini SDK directly or manage the specifics of its API for MCP-style interactions. They simply make standard MCP tool calls.
* **Centralized Configuration:** API keys, default model choices, safety settings, and other configurations are managed centrally within the `mcp-gemini-server`.
* **Rich Toolset:** Provides a broad set of pre-defined MCP tools for various Gemini features (text generation, chat, file handling, image generation, etc.), not just function calling.
* **Interoperability:** Enables any MCP-compatible client to leverage Gemini's capabilities without needing native Gemini SDK support.
### When to Choose Which Approach:
* **Direct SDK Usage (as in Google's MCP examples):**
* Suitable if you are building a client application (e.g., in Python or Node.js) and want fine-grained control over the Gemini API interaction directly within that client.
* Useful if you prefer to manage the Gemini SDK dependencies and logic within your client application and are primarily focused on function calling structured in an MCP-like way.
* **Using `mcp-gemini-server`:**
* Ideal if you want to expose Gemini capabilities to an existing MCP-compatible ecosystem (e.g., another LLM, a workflow automation system).
* Beneficial if you want to rapidly prototype or deploy Gemini features as tools without extensive client-side SDK integration.
* Preferable if you need a wider range of Gemini features exposed as consistent MCP tools and want to centralize the Gemini API interaction point.
### A Note on This Server's Own MCP Client Tools:
The `mcp-gemini-server` also includes tools like `mcpConnectToServer`, `mcpListServerTools`, and `mcpCallServerTool`. These tools allow *this server* to act as an MCP *client* to *other external* MCP servers. This is a distinct capability from how an MCP client would connect *to* `mcp-gemini-server` to utilize Gemini features.
## Environment Variables
### Required:
- `GOOGLE_GEMINI_API_KEY`: Your Google Gemini API key (required)
### Required for Production (unless NODE_ENV=test):
- `MCP_SERVER_HOST`: Server host address (e.g., "localhost")
- `MCP_SERVER_PORT`: Port for network transports (e.g., "8080")
- `MCP_CONNECTION_TOKEN`: A strong, unique shared secret token that clients must provide when connecting to this server. This is NOT provided by Google or any external service - you must generate it yourself using a cryptographically secure method. See the installation instructions (step 4) for generation methods. This token must be identical on both the server and all connecting clients.
### Optional - Gemini API Configuration:
- `GOOGLE_GEMINI_MODEL`: Default model to use (e.g., `gemini-1.5-pro-latest`, `gemini-1.5-flash`)
- `GOOGLE_GEMINI_DEFAULT_THINKING_BUDGET`: Default thinking budget in tokens (0-24576) for controlling model reasoning
### Optional - URL Context Configuration:
- `GOOGLE_GEMINI_ENABLE_URL_CONTEXT`: Enable URL context features (options: `true`, `false`; default: `false`)
- `GOOGLE_GEMINI_URL_MAX_COUNT`: Maximum URLs per request (default: `20`)
- `GOOGLE_GEMINI_URL_MAX_CONTENT_KB`: Maximum content size per URL in KB (default: `100`)
- `GOOGLE_GEMINI_URL_FETCH_TIMEOUT_MS`: Fetch timeout per URL in milliseconds (default: `10000`)
- `GOOGLE_GEMINI_URL_ALLOWED_DOMAINS`: Comma-separated list or JSON array of allowed domains (default: `*` for all domains)
- `GOOGLE_GEMINI_URL_BLOCKLIST`: Comma-separated list or JSON array of blocked domains (default: empty)
- `GOOGLE_GEMINI_URL_CONVERT_TO_MARKDOWN`: Convert HTML content to markdown (options: `true`, `false`; default: `true`)
- `GOOGLE_GEMINI_URL_INCLUDE_METADATA`: Include URL metadata in context (options: `true`, `false`; default: `true`)
- `GOOGLE_GEMINI_URL_ENABLE_CACHING`: Enable URL content caching (options: `true`, `false`; default: `true`)
- `GOOGLE_GEMINI_URL_USER_AGENT`: Custom User-Agent header for URL requests (default: `MCP-Gemini-Server/1.0`)
### Optional - Security Configuration:
- `ALLOWED_OUTPUT_PATHS`: A comma-separated list of absolute paths to directories where tools like `mcpCallServerTool` (with outputToFile parameter) and `writeToFileTool` are allowed to write files. Critical security feature to prevent unauthorized file writes. If not set, file output will be disabled for these tools.
### Optional - Server Configuration:
- `MCP_CLIENT_ID`: Default client ID used when this server acts as a client to other MCP servers (defaults to "gemini-sdk-client")
- `MCP_TRANSPORT`: Transport to use for MCP server (options: `stdio`, `sse`, `streamable`, `http`; default: `stdio`)
- IMPORTANT: SSE (Server-Sent Events) is NOT deprecated and remains a critical component of the MCP protocol
- SSE is particularly valuable for bidirectional communication, enabling features like dynamic tool updates and sampling
- Each transport type has specific valid use cases within the MCP ecosystem
- `MCP_LOG_LEVEL`: Log level for MCP operations (options: `debug`, `info`, `warn`, `error`; default: `info`)
- `MCP_ENABLE_STREAMING`: Enable SSE streaming for HTTP transport (options: `true`, `false`; default: `false`)
- `MCP_SESSION_TIMEOUT`: Session timeout in seconds for HTTP transport (default: `3600` = 1 hour)
- `SESSION_STORE_TYPE`: Session storage backend (`memory` or `sqlite`; default: `memory`)
- `SQLITE_DB_PATH`: Path to SQLite database file when using sqlite store (default: `./data/sessions.db`)
### Optional - GitHub Integration:
- `GITHUB_API_TOKEN`: Personal Access Token for GitHub API access (required for GitHub code review features). For public repos, token needs 'public_repo' and 'read:user' scopes. For private repos, token needs 'repo' scope.
### Optional - Legacy Server Configuration (Deprecated):
- `MCP_TRANSPORT_TYPE`: Deprecated - Use `MCP_TRANSPORT` instead
- `MCP_WS_PORT`: Deprecated - Use `MCP_SERVER_PORT` instead
- `ENABLE_HEALTH_CHECK`: Enable health check server (options: `true`, `false`; default: `true`)
- `HEALTH_CHECK_PORT`: Port for health check HTTP server (default: `3000`)
You can create a `.env` file in the root directory with these variables:
```env
# Required API Configuration
GOOGLE_GEMINI_API_KEY=your_api_key_here
# Required for Production (unless NODE_ENV=test)
MCP_SERVER_HOST=localhost
MCP_SERVER_PORT=8080
MCP_CONNECTION_TOKEN=your_secure_token_here
# Optional API Configuration
GOOGLE_GEMINI_MODEL=gemini-1.5-pro-latest
GOOGLE_GEMINI_DEFAULT_THINKING_BUDGET=4096
# Security Configuration
ALLOWED_OUTPUT_PATHS=/var/opt/mcp-gemini-server/outputs,/tmp/mcp-gemini-outputs # For mcpCallServerTool and writeToFileTool
# URL Context Configuration
GOOGLE_GEMINI_ENABLE_URL_CONTEXT=true # Enable URL context features
GOOGLE_GEMINI_URL_MAX_COUNT=20 # Maximum URLs per request
GOOGLE_GEMINI_URL_MAX_CONTENT_KB=100 # Maximum content size per URL in KB
GOOGLE_GEMINI_URL_FETCH_TIMEOUT_MS=10000 # Fetch timeout per URL in milliseconds
GOOGLE_GEMINI_URL_ALLOWED_DOMAINS=* # Allowed domains (* for all, or comma-separated list)
GOOGLE_GEMINI_URL_BLOCKLIST=malicious.com,spam.net # Blocked domains (comma-separated)
GOOGLE_GEMINI_URL_CONVERT_TO_MARKDOWN=true # Convert HTML to markdown
GOOGLE_GEMINI_URL_INCLUDE_METADATA=true # Include URL metadata in context
GOOGLE_GEMINI_URL_ENABLE_CACHING=true # Enable URL content caching
GOOGLE_GEMINI_URL_USER_AGENT=MCP-Gemini-Server/1.0 # Custom User-Agent
# Server Configuration
MCP_CLIENT_ID=gemini-sdk-client # Optional: Default client ID for MCP connections (defaults to "gemini-sdk-client")
MCP_TRANSPORT=stdio # Options: stdio, sse, streamable, http (replaced deprecated MCP_TRANSPORT_TYPE)
MCP_LOG_LEVEL=info # Optional: Log level for MCP operations (debug, info, warn, error)
MCP_ENABLE_STREAMING=true # Enable SSE streaming for HTTP transport
MCP_SESSION_TIMEOUT=3600 # Session timeout in seconds for HTTP transport
SESSION_STORE_TYPE=memory # Options: memory, sqlite
SQLITE_DB_PATH=./data/sessions.db # Path to SQLite database file when using sqlite store
ENABLE_HEALTH_CHECK=true
HEALTH_CHECK_PORT=3000
# GitHub Integration
GITHUB_API_TOKEN=your_github_token_here
```
## Security Considerations
This server implements several security measures to protect against common vulnerabilities. Understanding these security features is critical when deploying in production environments.
### File System Security
1. **Path Validation and Isolation**
- **ALLOWED_OUTPUT_PATHS**: Critical security feature that restricts where file writing tools can write files
- **Security Principle**: Files can only be created, read, or modified within explicitly allowed directories
- **Production Requirement**: Always use absolute paths to prevent potential directory traversal attacks
2. **Path Traversal Protection**
- The `FileSecurityService` implements robust path traversal protection by:
- Fully resolving paths to their absolute form
- Normalizing paths to handle ".." and "." segments properly
- Validating that normalized paths stay within allowed directories
- Checking both string-based prefixes and relative path calculations for redundant security
3. **Symlink Security**
- Symbolic links are fully resolved and checked against allowed directories
- Both the symlink itself and its target are validated
- Parent directory symlinks are iteratively checked to prevent circumvention
- Multi-level symlink chains are fully resolved before validation
### Authentication & Authorization
1. **Connection Tokens**
- `MCP_CONNECTION_TOKEN` provides basic authentication for clients connecting to this server
- Should be treated as a secret and use a strong, unique value in production
2. **API Key Security**
- `GOOGLE_GEMINI_API_KEY` grants access to Google Gemini API services
- Must be kept secure and never exposed in client-side code or logs
- Use environment variables or secure secret management systems to inject this value
### URL Context Security
1. **Multi-Layer URL Validation**
- **Protocol Validation**: Only HTTP/HTTPS protocols are allowed
- **Private Network Protection**: Blocks access to localhost, private IP ranges, and internal domains
- **Domain Control**: Configurable allowlist/blocklist with wildcard support
- **Suspicious Pattern Detection**: Identifies potential path traversal, dangerous characters, and malicious patterns
- **IDN Homograph Attack Prevention**: Detects potentially confusing Unicode domain names
2. **Rate Limiting and Resource Protection**
- **Per-domain rate limiting**: Default 10 requests per minute per domain
- **Content size limits**: Configurable maximum content size per URL (default 100KB)
- **Request timeout controls**: Prevents hanging requests (default 10 seconds)
- **Concurrent request limits**: Controlled batch processing to prevent overload
3. **Content Security**
- **Content type validation**: Only processes text-based content types
- **HTML sanitization**: Removes script tags, style blocks, and dangerous content
- **Metadata extraction**: Safely parses HTML metadata without executing code
- **Memory protection**: Content truncation prevents memory exhaustion attacks
### Network Security
1. **Transport Options**
- stdio: Provides process isolation when used as a spawned child process
- SSE/HTTP: Ensure proper network-level protection when exposing over networks
2. **Port Configuration**
- Configure firewall rules appropriately when exposing server ports
- Consider reverse proxies with TLS termination for production deployments
### Production Deployment Recommendations
1. **File Paths**
- Always use absolute paths for `ALLOWED_OUTPUT_PATHS`
- Use paths outside the application directory to prevent source code modification
- Restrict to specific, limited-purpose directories with appropriate permissions
- NEVER include sensitive system directories like "/", "/etc", "/usr", "/bin", or "/home"
2. **Process Isolation**
- Run the server with restricted user permissions
- Consider containerization (Docker) for additional isolation
3. **Secrets Management**
- Use a secure secrets management solution instead of .env files in production
- Rotate API keys and connection tokens regularly
4. **URL Context Security**
- Enable URL context only when needed: Set `GOOGLE_GEMINI_ENABLE_URL_CONTEXT=false` if not required
- Use restrictive domain allowlists: Avoid `GOOGLE_GEMINI_URL_ALLOWED_DOMAINS=*` in production
- Configure comprehensive blocklists: Add known malicious domains to `GOOGLE_GEMINI_URL_BLOCKLIST`
- Set conservative resource limits: Use appropriate values for `GOOGLE_GEMINI_URL_MAX_CONTENT_KB` and `GOOGLE_GEMINI_URL_MAX_COUNT`
- Monitor URL access patterns: Review logs for suspicious URL access attempts
- Consider network-level protection: Use firewalls or proxies to add additional URL filtering
## Error Handling
The server provides enhanced error handling using the MCP standard `McpError` type when tool execution fails. This object contains:
* `code`: An `ErrorCode` enum value indicating the type of error:
* `InvalidParams`: Parameter validation errors (wrong type, missing required field, etc.)
* `InvalidRequest`: General request errors, including safety blocks and not found resources
* `PermissionDenied`: Authentication or authorization failures
* `ResourceExhausted`: Rate limits, quotas, or resource capacity issues
* `FailedPrecondition`: Operations that require conditions that aren't met
* `InternalError`: Unexpected server or API errors
* `message`: A human-readable description of the error with specific details.
* `details`: (Optional) An object with more specific information from the Gemini SDK error.
### Implementation Details
The server uses a multi-layered approach to error handling:
1. **Validation Layer**: Zod schemas validate all parameters at both the tool level (MCP request) and service layer (before API calls).
2. **Error Classification**: A detailed error mapping system categorizes errors from the Google GenAI SDK into specific error types:
* `GeminiValidationError`: Parameter validation failures
* `GeminiAuthError`: Authentication issues
* `GeminiQuotaError`: Rate limiting and quota exhaustion
* `GeminiContentFilterError`: Content safety filtering
* `GeminiNetworkError`: Connection and timeout issues
* `GeminiModelError`: Model-specific problems
3. **Retry Mechanism**: Automatic retry with exponential backoff for transient errors:
* Network issues, timeouts, and rate limit errors are automatically retried
* Configurable retry parameters (attempts, delay, backoff factor)
* Jitter randomization to prevent synchronized retry attempts
* Detailed logging of retry attempts for debugging
**Common Error Scenarios:**
* **Authentication Failures:** `PermissionDenied` - Invalid API key, expired credentials, or unauthorized access.
* **Parameter Validation:** `InvalidParams` - Missing required fields, wrong data types, invalid values.
* **Safety Blocks:** `InvalidRequest` - Content blocked by safety filters with details indicating `SAFETY` as the block reason.
* **File/Cache Not Found:** `InvalidRequest` - Resource not found, with details about the missing resource.
* **Rate Limits:** `ResourceExhausted` - API quota exceeded or rate limits hit, with details about limits.
* **File API Unavailable:** `FailedPrecondition` - When attempting File API operations without a valid Google AI Studio key.
* **Path Traversal Security:** `InvalidParams` - Attempts to access audio files outside the allowed directory with details about the security validation failure.
* **Image/Audio Processing Errors:**
* `InvalidParams` - For format issues, size limitations, or invalid inputs
* `InternalError` - For processing failures during analysis
* `ResourceExhausted` - For resource-intensive operations exceeding limits
The server includes additional context in error messages to help with troubleshooting, including session IDs for chat-related errors and specific validation details for parameter errors.
Check the `message` and `details` fields of the returned `McpError` for specific troubleshooting information.
## Development and Testing
This server includes a comprehensive test suite to ensure functionality and compatibility with the Gemini API. The tests are organized into unit tests (for individual components) and integration tests (for end-to-end functionality).
### Test Structure
- **Unit Tests**: Located in `tests/unit/` - Test individual components in isolation with mocked dependencies
- **Integration Tests**: Located in `tests/integration/` - Test end-to-end functionality with real server interaction
- **Test Utilities**: Located in `tests/utils/` - Helper functions and fixtures for testing
### Running Tests
```bash
# Install dependencies first
npm install
# Run all tests
npm run test
# Run only unit tests
npm run test:unit
# Run only integration tests
npm run test:integration
# Run a specific test file
node --test --loader ts-node/esm tests/path/to/test-file.test.ts
```
### Testing Approach
1. **Service Mocking**: The tests use a combination of direct method replacement and mock interfaces to simulate the Gemini API response. This is particularly important for the `@google/genai` SDK (v0.10.0) which has a complex object structure.
2. **Environmental Variables**: Tests automatically check for required environment variables and will skip tests that require API keys if they're not available. This allows core functionality to be tested without credentials.
3. **Test Server**: Integration tests use a test server fixture that creates an isolated HTTP server instance with the MCP handler configured for testing.
4. **RetryService**: The retry mechanism is extensively tested to ensure proper handling of transient errors with exponential backoff, jitter, and configurable retry parameters.
5. **Image Generation**: Tests specifically address the complex interactions with the Gemini API for image generation, supporting both Gemini models and the dedicated Imagen 3.1 model.
### Test Environment Setup
For running tests that require API access, create a `.env.test` file in the project root with the following variables:
```env
# Required for basic API tests
GOOGLE_GEMINI_API_KEY=your_api_key_here
# Required for router tests
GOOGLE_GEMINI_MODEL=gemini-1.5-flash
```
The test suite will automatically detect available environment variables and skip tests that require missing configuration.
## Contributing
We welcome contributions to improve the MCP Gemini Server! This section provides guidelines for contributing to the project.
### Development Environment Setup
1. **Fork and Clone the Repository**
```bash
git clone https://github.com/yourusername/mcp-gemini-server.git
cd mcp-gemini-server
```
2. **Install Dependencies**
```bash
npm install
```
3. **Set Up Environment Variables**
Create a `.env` file in the project root with the necessary variables as described in the Environment Variables section.
4. **Build and Run**
```bash
npm run build
npm run dev
```
### Development Process
1. **Create a Feature Branch**
```bash
git checkout -b feature/your-feature-name
```
2. **Make Your Changes**
Implement your feature or fix, following the code style guidelines.
3. **Write Tests**
Add tests for your changes to ensure functionality and prevent regressions.
4. **Run Tests and Linting**
```bash
npm run test
npm run lint
npm run format
```
5. **Commit Your Changes**
Use clear, descriptive commit messages that explain the purpose of your changes.
### Testing Guidelines
- Write unit tests for all new functionality
- Update existing tests when modifying functionality
- Ensure all tests pass before submitting a pull request
- Include both positive and negative test cases
- Mock external dependencies to ensure tests can run without external services
### Pull Request Process
1. **Update Documentation**
Update the README.md and other documentation to reflect your changes.
2. **Submit a Pull Request**
- Provide a clear description of the changes
- Link to any related issues
- Explain how to test the changes
- Ensure all CI checks pass
3. **Code Review**
- Address any feedback from reviewers
- Make requested changes and update the PR
### Coding Standards
- Follow the existing code style (PascalCase for classes/interfaces/types, camelCase for functions/variables)
- Use strong typing with TypeScript interfaces
- Document public APIs with JSDoc comments
- Handle errors properly by extending base error classes
- Follow the service-based architecture with dependency injection
- Use Zod for schema validation
- Format code according to the project's ESLint and Prettier configuration
## Code Review Tools
The MCP Gemini Server provides powerful code review capabilities leveraging Gemini's models to analyze git diffs and GitHub repositories. These tools help identify potential issues, suggest improvements, and provide comprehensive feedback on code changes.
### Local Git Diff Review
Review local git changes directly from your command line:
```bash
# Using the included CLI script
./scripts/gemini-review.sh
# Options
./scripts/gemini-review.sh --focus=security --reasoning=high
```
The CLI script supports various options:
- `--focus=FOCUS`: Focus of the review (security, performance, architecture, bugs, general)
- `--model=MODEL`: Model to use (defaults to gemini-flash-2.0 for cost efficiency)
- `--reasoning=LEVEL`: Reasoning effort (none, low, medium, high)
- `--exclude=PATTERN`: Files to exclude using glob patterns
### GitHub Repository Review
Review GitHub repositories, branches, and pull requests using the following tools:
- **GitHub PR Review Tool**: Analyzes pull requests for issues and improvements
- **GitHub Repository Review Tool**: Analyzes entire repositories or branches
### Cost Optimization
By default, code review tools use the more cost-efficient `gemini-flash-2.0` model, which offers a good balance between cost and capability for most code review tasks. For particularly complex code bases or when higher reasoning depth is needed, you can specify more powerful models:
```bash
# Using a more powerful model for complex code
./scripts/gemini-review.sh --model=gemini-1.5-pro --reasoning=high
```
### Running Tests
Tests for the GitHub code review functionality can also use the cheaper model:
```bash
# Run tests with the default gemini-flash-2.0 model
npm run test:unit
```
## Server Features
### Health Check Endpoint
The server provides a built-in health check HTTP endpoint that can be used for monitoring and status checks. This is separate from the MCP server transport and runs as a lightweight HTTP server.
When enabled, you can access the health check at:
```
http://localhost:3000/health
```
The health check endpoint returns a JSON response with the following information:
```json
{
"status": "running",
"uptime": 1234, // Seconds since the server started
"transport": "StdioServerTransport", // Current transport type
"version": "0.1.0" // Server version
}
```
You can check the health endpoint using curl:
```bash
curl http://localhost:3000/health
```
You can configure the health check using these environment variables:
- `ENABLE_HEALTH_CHECK`: Set to "false" to disable the health check server (default: "true")
- `HEALTH_CHECK_PORT`: Port number for the health check server (default: 3000)
### Session Persistence
The server supports persistent session storage for HTTP/SSE transports, allowing sessions to survive server restarts and enabling horizontal scaling.
#### Storage Backends
1. **In-Memory Store (Default)**
- Sessions stored in server memory
- Fast performance for development
- Sessions lost on server restart
- No external dependencies
2. **SQLite Store**
- Sessions persisted to local SQLite database
- Survives server restarts
- Automatic cleanup of expired sessions
- Good for single-instance production deployments
#### Configuration
Enable SQLite session persistence:
```bash
export SESSION_STORE_TYPE=sqlite
export SQLITE_DB_PATH=./data/sessions.db # Optional, this is the default
```
The SQLite database file and directory will be created automatically on first use. The database includes:
- Automatic indexing for performance
- Built-in cleanup of expired sessions
- ACID compliance for data integrity
#### Session Lifecycle
- Sessions are created when clients connect via HTTP/SSE transport
- Each session has a configurable timeout (default: 1 hour)
- Session expiration is extended on each activity
- Expired sessions are automatically cleaned up every minute
### Graceful Shutdown
The server implements graceful shutdown handling for SIGTERM and SIGINT signals. When the server receives a shutdown signal:
1. It attempts to properly disconnect the MCP server transport
2. It closes the health check server if running
3. It logs the shutdown status
4. It exits with the appropriate exit code (0 for successful shutdown, 1 if errors occurred)
This ensures clean termination when the server is run in containerized environments or when stopped manually.
## Known Issues
* **Pagination Issues:** `gemini_listCaches` may not reliably return `nextPageToken` due to limitations in iterating the SDK's Pager object. A workaround is implemented but has limited reliability.
* **Path Requirements:** Audio transcription operations require absolute paths when run from the server environment. Relative paths are not supported.
* **File Size Limitations:** Audio files for transcription are limited to 20MB (original file size, before base64 encoding). The server reads the file and converts it to base64 internally. Larger files will be rejected with an error message.
* **API Compatibility:** Caching API is **not supported with Vertex AI credentials**, only Google AI Studio API keys.
* **Model Support:** This server is primarily tested and optimized for the latest Gemini 1.5 and 2.5 models. While other models should work, these models are the primary focus for testing and feature compatibility.
* **TypeScript Build Issues:** The TypeScript build may show errors primarily in test files. These are type compatibility issues that don't affect the runtime functionality. The server itself will function properly despite these build warnings.
* **Resource Usage:**
* Image processing requires significant resource usage, especially for large resolution images. Consider using smaller resolutions (512x512) for faster responses.
* Generating multiple images simultaneously increases resource usage proportionally.
* Audio transcription is limited to files under 20MB (original file size). The server reads files from disk and handles base64 conversion internally. Processing may take significant time and resources depending on file size and audio complexity.
* **Content Handling:**
* Base64-encoded images are streamed in chunks to handle large file sizes efficiently.
* Visual content understanding may perform differently across various types of visual content (charts vs. diagrams vs. documents).
* Audio transcription accuracy depends on audio quality, number of speakers, and background noise.
* **URL Context Features:**
* URL context is disabled by default and must be explicitly enabled via `GOOGLE_GEMINI_ENABLE_URL_CONTEXT=true`
* JavaScript-rendered content is not supported - only static HTML content is processed
* Some websites may block automated access or require authentication that is not currently supported
* Content extraction quality may vary depending on website structure and formatting
* Rate limiting per domain (10 requests/minute by default) may affect bulk processing scenarios
```
--------------------------------------------------------------------------------
/src/services/mcp/index.ts:
--------------------------------------------------------------------------------
```typescript
export * from "./McpClientService.js";
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
// Make sure to import type augmentation first
import "./types/googleGenAITypes.js";
// ... existing code ...
```
--------------------------------------------------------------------------------
/vitest.setup.ts:
--------------------------------------------------------------------------------
```typescript
// This file sets up Vitest environment for TypeScript
// With globals: true in vitest.config.ts, expect and vi are automatically available
```
--------------------------------------------------------------------------------
/src/services/session/index.ts:
--------------------------------------------------------------------------------
```typescript
export { SessionStore } from "./SessionStore.js";
export { InMemorySessionStore } from "./InMemorySessionStore.js";
export { SQLiteSessionStore } from "./SQLiteSessionStore.js";
```
--------------------------------------------------------------------------------
/src/tools/registration/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tool Registration Index
*
* Exports the new tool registration system components.
*/
export * from "./ToolRegistry.js";
export * from "./ToolAdapter.js";
export * from "./registerAllTools.js";
```
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["node", "vitest/globals"],
"skipLibCheck": true,
"noEmit": true,
"rootDir": "."
},
"include": [
"src/**/*",
"tests/**/*",
"vitest.setup.ts"
],
"exclude": [
"node_modules",
"dist"
]
}
```
--------------------------------------------------------------------------------
/src/services/index.ts:
--------------------------------------------------------------------------------
```typescript
// Export all services from this barrel file
export * from "./ExampleService.js";
export * from "./GeminiService.js"; // Export Gemini service
export * from "./SessionService.js";
export * from "./mcp/index.js"; // Export MCP services
// export * from './YourService.js'; // Add new services here
```
--------------------------------------------------------------------------------
/scripts/run-with-health-check.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
# Script to run the MCP server with health check enabled on a specific port
# Usage: ./scripts/run-with-health-check.sh [port]
# Default port if not specified
HEALTH_PORT=${1:-3000}
# Run the server with health check enabled
ENABLE_HEALTH_CHECK=true HEALTH_CHECK_PORT=$HEALTH_PORT node dist/server.js
```
--------------------------------------------------------------------------------
/tests/tsconfig.test.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"types": ["node", "vitest/globals"],
"allowImportingTsExtensions": true,
"rootDir": ".."
},
"include": [
"../src/**/*",
"./**/*",
"../node_modules/vitest/globals.d.ts"
],
"exclude": [
"../node_modules",
"../dist"
]
}
```
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
```typescript
// Export all utility functions and modules from this barrel file
export * from "./logger.js";
export * from "./errors.js";
export * from "./filePathSecurity.js";
export * from "./FileSecurityService.js";
export * from "./RetryService.js";
export * from "./healthCheck.js";
// export * from './contextSanitizer.js'; // Add other utils here
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine
WORKDIR /app
# Copy package files and install dependencies
COPY package.json package-lock.json ./
# Install dependencies without running lifecycle scripts
RUN npm ci --ignore-scripts
# Copy the rest of the app
COPY . .
# Build the TypeScript code
RUN npm run build
# Expose port if needed (optional)
# EXPOSE 3000
# Start the server
CMD ["node", "dist/server.js"]
```
--------------------------------------------------------------------------------
/tests/utils/vitest.d.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Type definitions for Vitest
* This helps to ensure we have proper types for our tests
*/
declare module "vitest" {
interface Mock {
<This, Args extends any[] = any[], Return = any>(
this: This,
...args: Args
): Return;
mockImplementation(fn: (...args: any[]) => any): this;
mockReturnValue(val: any): this;
mockResolvedValue(val: any): this;
mockRejectedValue(val: any): this;
}
function mocked<T>(item: T, deep?: boolean): T;
}
```
--------------------------------------------------------------------------------
/src/types/exampleServiceTypes.ts:
--------------------------------------------------------------------------------
```typescript
// Types specific to the ExampleService
/**
* Configuration options for ExampleService.
*/
export interface ExampleServiceConfig {
greeting: string;
enableDetailedLogs: boolean;
}
/**
* Data structure handled by ExampleService.
*/
export interface ExampleServiceData {
name: string;
message: string;
processedTimestamp: string;
metrics?: ExampleServiceMetrics;
}
/**
* Metrics collected during ExampleService processing.
*/
export interface ExampleServiceMetrics {
processingTimeMs: number;
}
```
--------------------------------------------------------------------------------
/src/utils/ToolError.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Custom error class for tool-specific errors.
* This is used by image feature tools and maintains compatibility
* with the error mapping system.
*/
export class ToolError extends Error {
public code: string;
public readonly details?: unknown;
constructor(message: string, options?: { code?: string; details?: unknown }) {
super(message);
this.name = "ToolError";
this.code = options?.code || "TOOL_ERROR";
this.details = options?.details;
// Capture stack trace (excluding constructor)
Error.captureStackTrace(this, this.constructor);
}
}
```
--------------------------------------------------------------------------------
/vitest-globals.d.ts:
--------------------------------------------------------------------------------
```typescript
/// <reference types="vitest/globals" />
// This file ensures vitest globals are available throughout the project
// when using vitest with globals: true configuration
declare global {
const describe: typeof import("vitest").describe;
const it: typeof import("vitest").it;
const test: typeof import("vitest").test;
const expect: typeof import("vitest").expect;
const beforeAll: typeof import("vitest").beforeAll;
const afterAll: typeof import("vitest").afterAll;
const beforeEach: typeof import("vitest").beforeEach;
const afterEach: typeof import("vitest").afterEach;
const vi: typeof import("vitest").vi;
}
export {};
```
--------------------------------------------------------------------------------
/tests/utils/debug-error.ts:
--------------------------------------------------------------------------------
```typescript
// Simple helper to debug errors
import { GeminiApiError, mapToMcpError } from "../../src/utils/errors.js";
// Create a test error with details
const errorWithDetails = new GeminiApiError("API error with details", {
key: "value",
});
const mappedError = mapToMcpError(errorWithDetails, "DEBUG_TOOL");
// Log the result for inspection
console.log("Original Error:", {
message: errorWithDetails.message,
details: errorWithDetails.details,
hasDetailsProperty: "details" in errorWithDetails,
});
console.log("Mapped Error:", {
code: mappedError.code,
message: mappedError.message,
details: mappedError.details,
hasDetailsProperty: "details" in mappedError,
});
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"allowJs": true,
"types": ["node", "vitest/globals"],
"paths": {
"@app/*": ["./src/*"],
"@tests/*": ["./tests/*"]
}
},
"ts-node": {
"transpileOnly": true,
"files": true
},
"include": [
"src/**/*",
"vitest-globals.d.ts"
],
"exclude": [
"node_modules",
"dist"
]
}
```
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from "vitest/config";
import path from "path";
export default defineConfig({
test: {
globals: true,
environment: "node",
setupFiles: ["./vitest.setup.ts"],
include: [
"**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
"**/*.test.vitest.ts",
"**/*.spec.vitest.ts",
],
coverage: {
provider: "v8",
reporter: ["text", "html", "lcov"],
exclude: ["node_modules/", "dist/", "tests/"],
},
alias: {
"@app": path.resolve(__dirname, "./src"),
"@tests": path.resolve(__dirname, "./tests"),
},
},
resolve: {
alias: {
"@app": path.resolve(__dirname, "./src"),
"@tests": path.resolve(__dirname, "./tests"),
},
},
});
```
--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpClientService } from "../services/mcp/McpClientService.js";
import { registerAllTools } from "./registration/registerAllTools.js";
/**
* Register all defined tools with the MCP server instance.
* This function serves as the central entry point for tool registration.
* It uses the new tool registration system for improved organization and type safety.
*
* @param server The McpServer instance
* @returns The McpClientService instance for managing MCP client connections
*/
export function registerTools(server: McpServer): McpClientService {
return registerAllTools(server);
}
// Re-export schema components
export * from "./schemas/index.js";
// Re-export registration utilities
export * from "./registration/index.js";
```
--------------------------------------------------------------------------------
/tests/utils/integration-types.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Types for integration tests
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpClientService } from "../../src/services/mcp/McpClientService.js";
/**
* Tool processor function type
*/
export type ToolProcessor = (args: any) => Promise<{
content: Array<{
text: string;
type: string;
}>;
functionCall?: any;
}>;
/**
* Collection of tool processors for testing
*/
export interface ToolProcessors {
connect: ToolProcessor;
listTools: ToolProcessor;
callServerTool: ToolProcessor;
disconnect: ToolProcessor;
writeToFile: ToolProcessor;
}
/**
* Mock server tool handler
*/
export type MockServerToolHandler = (
server: McpServer,
mcpClientService: McpClientService
) => void;
/**
* Tool registration function
*/
export type ToolRegistrationFn = (
server: McpServer,
service: McpClientService
) => unknown;
```
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
```typescript
// Export all types and interfaces from this barrel file
export * from "./exampleServiceTypes.js";
export * from "./geminiServiceTypes.js"; // Exports GeminiServiceConfig, CachedContentMetadata, PartSchema, ContentSchema
export * from "./serverTypes.js"; // Server state and service interface types
// Export type-safe schema types from tool schemas
export type {
FunctionParameter,
InferredFunctionParameter,
} from "../tools/schemas/CommonSchemas.js";
// Define common types used across services/tools if any
export interface CommonContext {
sessionId?: string;
userId?: string;
}
/**
* Represents the input structure for a function response sent from the client to the server.
* Used by the gemini_sendFunctionResult tool.
*/
export interface FunctionResponseInput {
/** The name of the function that was called by the model. */
name: string;
/** The JSON object result returned by the function execution. */
response: Record<string, unknown>;
}
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
type: object
required:
- GOOGLE_GEMINI_API_KEY
properties:
GOOGLE_GEMINI_API_KEY:
type: string
description: Your API key from Google AI Studio.
GOOGLE_GEMINI_MODEL:
type: string
default: gemini-1.5-flash
description: Default Gemini model. Optional; if not provided, 'gemini-1.5-flash'
is used.
commandFunction:
# A JS function that produces the CLI command based on the given config to start the MCP on stdio.
|-
(config) => ({ command: 'node', args: ['dist/server.js'], env: { GOOGLE_GEMINI_API_KEY: config.GOOGLE_GEMINI_API_KEY, GOOGLE_GEMINI_MODEL: config.GOOGLE_GEMINI_MODEL || 'gemini-1.5-flash' } })
exampleConfig:
GOOGLE_GEMINI_API_KEY: your-api-key-here
GOOGLE_GEMINI_MODEL: gemini-1.5-flash
```
--------------------------------------------------------------------------------
/src/tools/exampleToolParams.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
export const TOOL_NAME = "exampleTool";
export const TOOL_DESCRIPTION =
"An example tool that takes a name and returns a greeting message. Demonstrates the basic structure of an MCP tool using Zod for parameter definition.";
// Define parameters using Zod for validation and description generation
// We need to define this as a raw object with Zod validators (not wrapped in z.object)
// to be compatible with the MCP server.tool() method
export const TOOL_PARAMS = {
name: z
.string()
.min(1, { message: "Name cannot be empty." })
.max(50, { message: "Name cannot exceed 50 characters." })
.describe(
"The name to include in the greeting message. Required, 1-50 characters."
),
// Example optional parameter
language: z
.enum(["en", "es", "fr"])
.optional()
.describe(
"Optional language code for the greeting (e.g., 'en', 'es', 'fr'). Defaults to 'en' if not provided or invalid."
),
};
// For internal validation within the tool, we can create a complete schema
export const exampleToolSchema = z.object(TOOL_PARAMS);
```
--------------------------------------------------------------------------------
/src/tools/geminiGenericParamSchemas.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import {
FunctionParameterTypeSchema,
FunctionParameterSchema,
FunctionParameterPropertiesSchema,
FunctionDeclarationSchema,
ToolConfigSchema,
FunctionParameter,
} from "./schemas/CommonSchemas.js";
import { ToolSchema } from "./schemas/ToolSchemas.js";
// Re-export centralized schemas for backward compatibility
export const functionParameterTypeSchema = FunctionParameterTypeSchema;
export const functionParameterSchema = FunctionParameterSchema;
export const functionParameterPropertiesSchema =
FunctionParameterPropertiesSchema;
export const functionDeclarationSchema = FunctionDeclarationSchema;
export const toolConfigSchema = ToolConfigSchema;
export { ToolSchema };
// Type exports for better type inference
export type FunctionParameterType = z.infer<typeof FunctionParameterTypeSchema>;
export type { FunctionParameter };
export type FunctionParameterProperties = z.infer<
typeof FunctionParameterPropertiesSchema
>;
export type FunctionDeclaration = z.infer<typeof FunctionDeclarationSchema>;
export type Tool = z.infer<typeof ToolSchema>;
export type ToolConfig = z.infer<typeof ToolConfigSchema>;
```
--------------------------------------------------------------------------------
/src/types/node-fetch.d.ts:
--------------------------------------------------------------------------------
```typescript
declare module "node-fetch" {
export interface HeadersInit {
[key: string]: string | string[];
}
export interface RequestInit {
body?: string | Blob | ArrayBuffer | NodeJS.ReadableStream;
headers?: HeadersInit;
method?: string;
redirect?: string;
signal?: AbortSignal;
timeout?: number;
compress?: boolean;
size?: number;
follow?: number;
agent?: import("http").Agent | import("https").Agent | false;
}
export default function fetch(
url: string | URL,
options?: RequestInit
): Promise<Response>;
export class Response {
ok: boolean;
status: number;
statusText: string;
headers: Headers;
json(): Promise<unknown>;
text(): Promise<string>;
buffer(): Promise<Buffer>;
arrayBuffer(): Promise<ArrayBuffer>;
clone(): Response;
}
export class Headers {
constructor(init?: HeadersInit);
get(name: string): string | null;
has(name: string): boolean;
set(name: string, value: string): void;
append(name: string, value: string): void;
delete(name: string): void;
forEach(callback: (value: string, name: string) => void): void;
}
}
```
--------------------------------------------------------------------------------
/src/createServer.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerTools } from "./tools/index.js";
import { logger } from "./utils/index.js";
import { McpClientService } from "./services/mcp/McpClientService.js";
/**
* Result object containing the created server and client service instances
*/
export interface ServerCreationResult {
server: McpServer;
mcpClientService: McpClientService;
}
/**
* Creates and configures an MCP server instance.
* This is the central function for server creation and tool registration.
* Note: ConfigurationManager is imported directly by services as needed.
* @returns {ServerCreationResult} Object containing the configured MCP server and client service instances
*/
export function createServer(): ServerCreationResult {
logger.info("Creating MCP server instance...");
// Initialize the server
const server = new McpServer({
name: "mcp-server",
version: "1.0.0",
description: "MCP Server based on recommended practices",
});
// Register all tools and get the McpClientService instance
const mcpClientService = registerTools(server);
logger.info("MCP server instance created successfully.");
return { server, mcpClientService };
}
```
--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------
```typescript
import chalk from "chalk"; // Using chalk for colored output
// Simple console logger with levels and colors
// Define log levels (optional, for potential filtering later)
enum LogLevel {
DEBUG,
INFO,
WARN,
ERROR,
}
// Basic configuration (could be enhanced)
const currentLogLevel = LogLevel.DEBUG; // Show all logs by default
export const logger = {
debug: (message: string, ...args: unknown[]) => {
if (currentLogLevel <= LogLevel.DEBUG) {
console.error(chalk.gray(`[DEBUG] `), ...args);
}
},
info: (message: string, ...args: unknown[]) => {
if (currentLogLevel <= LogLevel.INFO) {
console.error(chalk.blue(`[INFO] `), ...args);
}
},
warn: (message: string, ...args: unknown[]) => {
if (currentLogLevel <= LogLevel.WARN) {
console.error(chalk.yellow(`[WARN] `), ...args);
}
},
error: (message: string, ...args: unknown[]) => {
if (currentLogLevel <= LogLevel.ERROR) {
// Log error message and stack trace if available
console.error(chalk.red(`[ERROR] `), ...args);
const errorArg = args.find((arg) => arg instanceof Error);
if (errorArg instanceof Error && errorArg.stack) {
console.error(chalk.red(errorArg.stack));
}
}
},
};
```
--------------------------------------------------------------------------------
/src/services/session/InMemorySessionStore.ts:
--------------------------------------------------------------------------------
```typescript
import { SessionStore } from "./SessionStore.js";
import { SessionState } from "../SessionService.js";
/**
* In-memory implementation of SessionStore.
* This is the default implementation that stores sessions in a Map.
*/
export class InMemorySessionStore implements SessionStore {
private sessions: Map<string, SessionState> = new Map();
async initialize(): Promise<void> {
// No initialization needed for in-memory store
}
async set(sessionId: string, session: SessionState): Promise<void> {
this.sessions.set(sessionId, session);
}
async get(sessionId: string): Promise<SessionState | null> {
return this.sessions.get(sessionId) || null;
}
async delete(sessionId: string): Promise<boolean> {
return this.sessions.delete(sessionId);
}
async deleteExpired(now: number): Promise<number> {
let deletedCount = 0;
for (const [sessionId, session] of this.sessions.entries()) {
if (now > session.expiresAt) {
this.sessions.delete(sessionId);
deletedCount++;
}
}
return deletedCount;
}
async count(): Promise<number> {
return this.sessions.size;
}
async close(): Promise<void> {
// No cleanup needed for in-memory store
this.sessions.clear();
}
}
```
--------------------------------------------------------------------------------
/src/tools/schemas/ToolSchemas.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tool Schemas - Gemini API Specific Tool Types
*
* This file contains the schema definitions for tools compatible with the Gemini API.
*/
import { z } from "zod";
import {
FunctionParameterTypeSchema,
FunctionParameterSchema,
FunctionParameterPropertiesSchema,
FunctionDeclarationSchema,
ToolConfigSchema,
} from "./CommonSchemas.js";
/**
* Complete schema for a tool with function declarations
*/
export const ToolSchema = z
.object({
functionDeclarations: z
.array(FunctionDeclarationSchema)
.optional()
.describe("List of function declarations for this tool."),
// Can add other tool types like Retrieval, GoogleSearchRetrieval if needed
})
.describe("Represents a tool definition containing function declarations.");
/**
* Schema for a tool response
*/
export const ToolResponseSchema = z
.object({
name: z.string().describe("The name of the tool that was called"),
response: z.any().describe("The response returned by the tool"),
})
.describe("Response from a tool execution");
// Export for direct use in tool implementations
export {
FunctionParameterTypeSchema,
FunctionParameterSchema,
FunctionParameterPropertiesSchema,
FunctionDeclarationSchema,
ToolConfigSchema,
};
```
--------------------------------------------------------------------------------
/src/tools/schemas/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tool Schemas Index
*
* This barrel file exports all standardized schema definitions
* for tools, providing a central access point.
*/
// Base schema pattern
export * from "./BaseToolSchema.js";
// Common shared schema definitions
export * from "./CommonSchemas.js";
// Tool-specific schemas
export * from "./ToolSchemas.js";
// Note: Example tool params removed as per refactoring
// Re-export other schemas with namespacing to avoid export conflicts
import * as GeminiGenerateContentConsolidatedParamsModule from "../geminiGenerateContentConsolidatedParams.js";
export { GeminiGenerateContentConsolidatedParamsModule };
import * as GeminiChatParamsModule from "../geminiChatParams.js";
export { GeminiChatParamsModule };
import * as GeminiCacheParamsModule from "../geminiCacheParams.js";
export { GeminiCacheParamsModule };
import * as GeminiCodeReviewParamsModule from "../geminiCodeReviewParams.js";
export { GeminiCodeReviewParamsModule };
import * as McpClientParamsModule from "../mcpClientParams.js";
export { McpClientParamsModule };
import * as WriteToFileParamsModule from "./writeToFileParams.js";
export { WriteToFileParamsModule };
// Add exports for other tool parameter schemas as they are added
// export * from "./yourNewToolParams.js";
```
--------------------------------------------------------------------------------
/src/services/session/SessionStore.ts:
--------------------------------------------------------------------------------
```typescript
import { SessionState } from "../SessionService.js";
/**
* Interface for session storage implementations.
* This allows for different storage backends (in-memory, SQLite, Redis, etc.)
*/
export interface SessionStore {
/**
* Store a session
* @param sessionId The session identifier
* @param session The session state to store
*/
set(sessionId: string, session: SessionState): Promise<void>;
/**
* Retrieve a session
* @param sessionId The session identifier
* @returns The session state or null if not found
*/
get(sessionId: string): Promise<SessionState | null>;
/**
* Delete a session
* @param sessionId The session identifier
* @returns True if the session was deleted, false if it didn't exist
*/
delete(sessionId: string): Promise<boolean>;
/**
* Delete all expired sessions
* @param now Current timestamp in milliseconds
* @returns Number of sessions deleted
*/
deleteExpired(now: number): Promise<number>;
/**
* Get the count of active sessions
* @returns Number of sessions in the store
*/
count(): Promise<number>;
/**
* Initialize the store (create tables, connect, etc.)
*/
initialize(): Promise<void>;
/**
* Close/cleanup the store
*/
close(): Promise<void>;
}
```
--------------------------------------------------------------------------------
/src/tools/schemas/writeToFileParams.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import { createToolSchema } from "./BaseToolSchema.js";
import { FilePathSchema, FileOverwriteSchema } from "./CommonSchemas.js";
const TOOL_NAME = "write_to_file";
const TOOL_DESCRIPTION =
"Writes the given text content to a specified file path. The path must be within an allowed directory.";
// Text-only encoding schema for this tool
const TextEncodingSchema = z
.enum(["utf8"])
.optional()
.default("utf8")
.describe("Encoding of the content. Only UTF-8 text encoding is supported.");
const TOOL_PARAMS = {
filePath: FilePathSchema.describe(
"The path to the file where content will be written."
),
content: z.string().describe("The text content to write to the file."),
encoding: TextEncodingSchema,
overwriteFile: FileOverwriteSchema,
};
// Create standardized schema with helper function
const schema = createToolSchema(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS);
// Export all schema components
export const {
TOOL_NAME: exportedToolName,
TOOL_DESCRIPTION: exportedToolDescription,
TOOL_PARAMS: exportedToolParams,
toolSchema: writeToFileSchema,
ToolParams: WriteToFileParams,
} = schema;
// For backward compatibility
export {
exportedToolName as TOOL_NAME,
exportedToolDescription as TOOL_DESCRIPTION,
exportedToolParams as TOOL_PARAMS,
};
```
--------------------------------------------------------------------------------
/src/services/gemini/GeminiTypes.ts:
--------------------------------------------------------------------------------
```typescript
import type {
GenerationConfig,
SafetySetting,
Content,
Part,
Tool,
ToolConfig,
FunctionCall,
} from "@google/genai";
// Import ThinkingConfig and ExtendedGenerationConfig from our types
import type { ThinkingConfig } from "../../types/googleGenAITypes.js";
// Re-export types from Google GenAI SDK and our custom types
// Note: We're re-exporting the ExtendedGenerationConfig as GenerationConfig
// to ensure consistent usage across the codebase
export type {
GenerationConfig as BaseGenerationConfig,
SafetySetting,
Content,
Part,
Tool,
ToolConfig,
FunctionCall,
ThinkingConfig,
};
// Re-export our extended GenerationConfig as the default GenerationConfig
export type { GenerationConfig } from "../../types/googleGenAITypes.js";
// Extend GenerationConfig to include thinkingBudget property
declare module "@google/genai" {
interface GenerationConfig {
thinkingBudget?: number;
}
}
// Type-safe resource IDs
export type CacheId = `cachedContents/${string}`;
// Export the ChatSession interface for use across services
export interface ChatSession {
model: string;
config: {
history?: Content[];
generationConfig?: GenerationConfig;
safetySettings?: SafetySetting[];
tools?: Tool[];
systemInstruction?: Content;
cachedContent?: string;
thinkingConfig?: ThinkingConfig;
};
history: Content[];
}
```
--------------------------------------------------------------------------------
/src/types/serverTypes.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Server-related type definitions for the MCP Gemini Server
*/
import type { Server as HttpServer } from "http";
import type { Transport } from "@modelcontextprotocol/sdk/server/mcp.js";
/**
* Interface for session service implementations
*/
export interface SessionServiceLike {
stopCleanupInterval(): void;
}
/**
* Interface for MCP client service implementations
*/
export interface McpClientServiceLike {
disconnect(serverId: string): boolean;
closeAllConnections(): void;
}
/**
* Interface for server implementations that can be disconnected
*/
export interface ServerLike {
disconnect?(): Promise<void> | void;
}
/**
* Server state interface for tracking the overall server state
*/
export interface ServerState {
isRunning: boolean;
startTime: number | null;
transport: Transport | null;
server: ServerLike | null;
healthCheckServer: HttpServer | null;
mcpClientService: McpClientServiceLike | null;
sessionService?: SessionServiceLike | null;
httpServer?: HttpServer | null;
}
/**
* Test-specific server state type that makes mcpClientService optional
*/
export type TestServerState = Omit<ServerState, "mcpClientService"> & {
mcpClientService?: McpClientServiceLike | null;
};
/**
* JSON-RPC 2.0 initialize request structure
*/
export interface JsonRpcInitializeRequest {
jsonrpc: "2.0";
method: "initialize";
id: string | number;
params?: Record<string, unknown>;
}
```
--------------------------------------------------------------------------------
/src/tools/schemas/BaseToolSchema.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Base Tool Schema Pattern
*
* This file establishes the standardized pattern for defining tool parameter schemas.
* All tool parameter definitions should follow this pattern for consistency.
*/
import { z } from "zod";
/**
* Interface that defines the standard exports for all tool parameter files
*/
export interface ToolSchemaDefinition<T extends z.ZodRawShape> {
/**
* The tool name used for registration
*/
TOOL_NAME: string;
/**
* The tool description
*/
TOOL_DESCRIPTION: string;
/**
* The tool parameters as a Zod schema object for direct use with MCP server.tool()
*/
TOOL_PARAMS: T;
/**
* Complete Zod schema for validation (z.object(TOOL_PARAMS))
*/
toolSchema: z.ZodObject<T>;
/**
* TypeScript type for parameters derived from the schema
*/
ToolParams: z.infer<z.ZodObject<T>>;
}
/**
* Helper function to create a standardized tool schema definition
* @param name The tool name
* @param description The tool description
* @param params The Zod schema parameters
* @returns A standardized tool schema definition
*/
export function createToolSchema<T extends z.ZodRawShape>(
name: string,
description: string,
params: T
): ToolSchemaDefinition<T> {
const toolSchema = z.object(params);
return {
TOOL_NAME: name,
TOOL_DESCRIPTION: description,
TOOL_PARAMS: params,
toolSchema,
// This is a type-level property, not a runtime value
ToolParams: {} as z.infer<typeof toolSchema>,
};
}
```
--------------------------------------------------------------------------------
/src/types/gitdiff-parser.d.ts:
--------------------------------------------------------------------------------
```typescript
// Type definitions for gitdiff-parser
declare module "gitdiff-parser" {
export type ChangeType = "insert" | "delete" | "normal";
export interface InsertChange {
type: "insert";
content: string;
lineNumber: number;
isInsert: true;
}
export interface DeleteChange {
type: "delete";
content: string;
lineNumber: number;
isDelete: true;
}
export interface NormalChange {
type: "normal";
content: string;
isNormal: true;
oldLineNumber: number;
newLineNumber: number;
}
export type Change = InsertChange | DeleteChange | NormalChange;
export interface Hunk {
content: string;
oldStart: number;
newStart: number;
oldLines: number;
newLines: number;
changes: Change[];
}
export type FileType = "add" | "delete" | "modify" | "rename" | "copy";
export interface File {
hunks: Hunk[];
oldEndingNewLine: boolean;
newEndingNewLine: boolean;
oldMode: string;
newMode: string;
similarity?: number;
oldRevision: string;
newRevision: string;
oldPath: string;
newPath: string;
isBinary?: boolean;
type: FileType;
}
/**
* Parse a git diff string into a structured format
* @param diffStr Raw git diff string
* @returns Array of File objects representing the parsed diff
*/
export function parse(diffStr: string): File[];
// Export as ES module default export
const _default: {
parse(source: string): File[];
};
export default _default;
}
```
--------------------------------------------------------------------------------
/tests/basic-router.test.vitest.ts:
--------------------------------------------------------------------------------
```typescript
// Using vitest globals - see vitest.config.ts globals: true
import { GoogleGenAI } from "@google/genai";
import { GeminiChatService } from "../src/services/gemini/GeminiChatService.js";
import { RouteMessageParams } from "../src/services/GeminiService.js";
import { config } from "dotenv";
// Load environment variables
config();
// Simple test to check the router functionality
describe("Basic Router Test", () => {
it("should route messages correctly", async () => {
// Get API key from environment
const apiKey = process.env.GOOGLE_GEMINI_API_KEY;
// Skip if no API key
if (!apiKey) {
console.log("Skipping test, no API key");
return;
}
// Initialize Google GenAI
const genAI = new GoogleGenAI({ apiKey });
// Create chat service
const chatService = new GeminiChatService(genAI, "gemini-1.5-pro");
// Create router params
const params: RouteMessageParams = {
message: "What is the capital of France?",
models: ["gemini-1.5-pro", "gemini-1.5-flash"],
defaultModel: "gemini-1.5-pro",
};
try {
// Call route message
const result = await chatService.routeMessage(params);
// Check that we got a response
expect(result.response).toBeTruthy();
expect(result.chosenModel).toBeTruthy();
// Should be one of our models
expect(
["gemini-1.5-pro", "gemini-1.5-flash"].includes(result.chosenModel)
).toBeTruthy();
console.log(`Chosen model: ${result.chosenModel}`);
console.log(
`Response text: ${result.response.text?.substring(0, 50)}...`
);
} catch (error) {
console.error("Router test failed:", error);
throw error;
}
});
});
```
--------------------------------------------------------------------------------
/tests/unit/tools/schemas/BaseToolSchema.test.vitest.ts:
--------------------------------------------------------------------------------
```typescript
// Using vitest globals - see vitest.config.ts globals: true
import { z } from "zod";
import { createToolSchema } from "../../../../src/tools/schemas/BaseToolSchema.js";
/**
* Tests for the BaseToolSchema module, focusing on the createToolSchema factory
* function and ensuring it produces correctly structured tool schema definitions.
*/
describe("BaseToolSchema", () => {
describe("createToolSchema", () => {
it("should create a valid tool schema definition", () => {
const testParams = {
name: z.string().min(1),
count: z.number().int().positive(),
isEnabled: z.boolean().optional(),
};
const result = createToolSchema(
"testTool",
"A tool for testing",
testParams
);
expect(result).toHaveProperty("TOOL_NAME", "testTool");
expect(result).toHaveProperty("TOOL_DESCRIPTION", "A tool for testing");
expect(result).toHaveProperty("TOOL_PARAMS");
expect(result).toHaveProperty("toolSchema");
});
it("should create a schema that validates correctly", () => {
const testParams = {
name: z.string().min(1),
count: z.number().int().positive(),
};
const { toolSchema } = createToolSchema(
"testTool",
"A tool for testing",
testParams
);
// Valid data
const validData = { name: "test", count: 42 };
expect(toolSchema.safeParse(validData).success).toBe(true);
// Invalid data - missing required field
const missingField = { name: "test" };
expect(toolSchema.safeParse(missingField).success).toBe(false);
// Invalid data - wrong type
const wrongType = { name: "test", count: "42" };
expect(toolSchema.safeParse(wrongType).success).toBe(false);
});
});
});
```
--------------------------------------------------------------------------------
/tests/utils/error-helpers.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Error testing utilities for MCP Gemini Server tests
*
* This module provides helpers for testing error handling and ensuring
* proper instanceof checks work consistently.
*/
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
/**
* Helper function to reliably determine if an object is an McpError
*
* This works around possible ESM/TypeScript instanceof issues by checking
* both constructor name and presence of expected properties.
*
* @param obj - Object to check
* @returns True if the object appears to be an McpError
*/
export function isMcpError(obj: unknown): boolean {
if (obj === null || typeof obj !== "object") {
return false;
}
// First try the direct instanceof check
const isInstanceOf = obj instanceof McpError;
// If that works, great! Otherwise fall back to checking properties
if (isInstanceOf) {
return true;
}
// Manual property checks as fallback
const errorLike = obj as { code?: unknown; message?: unknown };
return (
obj !== null &&
typeof obj === "object" &&
"code" in obj &&
"message" in obj &&
typeof errorLike.code === "string" &&
typeof errorLike.message === "string" &&
Object.values(ErrorCode).includes(errorLike.code as ErrorCode)
);
}
/**
* Ensures that an object is treated as an McpError instance
*
* If the object is not originally recognized as an McpError,
* this function attempts to reconstruct it properly.
*
* @param obj - Object to convert
* @returns Same object or a reconstructed McpError
*/
export function ensureMcpError(obj: unknown): McpError {
if (obj instanceof McpError) {
return obj;
}
if (obj && typeof obj === "object" && "code" in obj && "message" in obj) {
const errObj = obj as {
code: unknown;
message: unknown;
details?: unknown;
};
return new McpError(
errObj.code as ErrorCode,
errObj.message as string,
errObj.details
);
}
// If all else fails, create a generic error
return new McpError(
ErrorCode.InternalError,
typeof obj === "string" ? obj : "Unknown error"
);
}
```
--------------------------------------------------------------------------------
/src/utils/healthCheck.ts:
--------------------------------------------------------------------------------
```typescript
import http from "http";
import { logger } from "./logger.js";
import type { ServerState, TestServerState } from "../types/serverTypes.js";
// Re-export types for backward compatibility
export type { ServerState, TestServerState };
// Reference to the server state from server.ts
// This will be set from server.ts
let serverStateRef: ServerState | null = null;
export const setServerState = (state: ServerState | TestServerState) => {
// For tests with TestServerState, add mcpClientService if not provided
if (!("mcpClientService" in state)) {
(state as ServerState).mcpClientService = null;
}
serverStateRef = state as ServerState;
};
export const getHealthStatus = () => {
if (!serverStateRef || !serverStateRef.isRunning) {
return {
status: "stopped",
uptime: 0,
};
}
const uptime = serverStateRef.startTime
? Math.floor((Date.now() - serverStateRef.startTime) / 1000)
: 0;
return {
status: "running",
uptime,
transport: serverStateRef.transport?.constructor.name || "unknown",
version: process.env.npm_package_version || "unknown",
};
};
/**
* Starts an HTTP server for health checks
* This runs independently of the MCP server transport
*/
export const startHealthCheckServer = () => {
const port = parseInt(process.env.HEALTH_CHECK_PORT || "3000", 10);
const server = http.createServer((req, res) => {
// Simple routing
if (req.url === "/health" || req.url === "/") {
const health = getHealthStatus();
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(health));
} else {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Not found" }));
}
});
server.on("error", (error: NodeJS.ErrnoException) => {
if (error.code === "EADDRINUSE") {
logger.error(`Health check server port ${port} is already in use`);
} else {
logger.error(`Health check server error: ${error.message}`);
}
});
server.listen(port, () => {
logger.info(`Health check server listening on port ${port}`);
});
return server;
};
```
--------------------------------------------------------------------------------
/src/services/ExampleService.ts:
--------------------------------------------------------------------------------
```typescript
import { ConfigurationManager } from "../config/ConfigurationManager.js";
import { ExampleServiceConfig, ExampleServiceData } from "../types/index.js";
import { logger } from "../utils/index.js";
import { ValidationError } from "../utils/errors.js";
/**
* Example service demonstrating the pattern.
*/
export class ExampleService {
private readonly config: Required<ExampleServiceConfig>;
constructor(config?: Partial<ExampleServiceConfig>) {
const configManager = ConfigurationManager.getInstance();
const defaultConfig = configManager.getExampleServiceConfig();
// Allow overriding defaults via constructor injection
this.config = { ...defaultConfig, ...config };
logger.info("ExampleService initialized.");
if (this.config.enableDetailedLogs) {
logger.debug("ExampleService Config:", this.config);
}
}
/**
* Processes example data.
* @param inputData - Input data, expected to match ExampleServiceData structure partially.
* @returns A promise resolving to the processed ExampleServiceData.
* @throws ValidationError if input is invalid.
*/
public async processExample(inputData: unknown): Promise<ExampleServiceData> {
const startTime = Date.now();
// Basic validation (could use Zod here for more robustness)
const data = inputData as Partial<ExampleServiceData>;
if (!data || typeof data.name !== "string" || data.name.trim() === "") {
throw new ValidationError(
"Invalid input: 'name' property is required and must be a non-empty string."
);
}
const name = data.name.trim();
const message = `${this.config.greeting}, ${name}!`; // Escaped PowerShell variable syntax
if (this.config.enableDetailedLogs) {
logger.debug(`Processing name: ${name}`);
}
// Simulate some async work
await new Promise((resolve) => setTimeout(resolve, 20));
const result: ExampleServiceData = {
name: name,
message: message,
processedTimestamp: new Date().toISOString(),
metrics: {
processingTimeMs: Date.now() - startTime,
},
};
logger.info(`Example processed for: ${name}`);
return result;
}
}
```
--------------------------------------------------------------------------------
/src/types/micromatch.d.ts:
--------------------------------------------------------------------------------
```typescript
// Type definitions for micromatch
declare module "micromatch" {
interface MicromatchOptions {
basename?: boolean;
bash?: boolean;
dot?: boolean;
posix?: boolean;
nocase?: boolean;
noextglob?: boolean;
nonegate?: boolean;
noglobstar?: boolean;
nobrace?: boolean;
regex?: boolean;
unescape?: boolean;
contains?: boolean;
matchBase?: boolean;
onMatch?: (match: string) => void;
onResult?: (result: string) => void;
[key: string]: unknown;
}
interface Micromatch {
(
list: string[],
patterns: string | string[],
options?: MicromatchOptions
): string[];
match(
list: string[],
patterns: string | string[],
options?: MicromatchOptions
): string[];
isMatch(
str: string,
patterns: string | string[],
options?: MicromatchOptions
): boolean;
contains(
str: string,
pattern: string,
options?: MicromatchOptions
): boolean;
matcher(
pattern: string,
options?: MicromatchOptions
): (str: string) => boolean;
any(str: string, patterns: string[], options?: MicromatchOptions): boolean;
not(
list: string[],
patterns: string | string[],
options?: MicromatchOptions
): string[];
filter(
patterns: string | string[],
options?: MicromatchOptions
): (str: string) => boolean;
some(
list: string[],
patterns: string | string[],
options?: MicromatchOptions
): boolean;
every(
list: string[],
patterns: string | string[],
options?: MicromatchOptions
): boolean;
all(str: string, patterns: string[], options?: MicromatchOptions): boolean;
capture(
str: string,
pattern: string,
options?: MicromatchOptions
): string[] | null;
test(str: string, pattern: string, options?: MicromatchOptions): boolean;
matchKeys(
obj: object,
patterns: string | string[],
options?: MicromatchOptions
): object;
braces(str: string, options?: MicromatchOptions): string[];
braceExpand(str: string, options?: MicromatchOptions): string[];
makeRe(pattern: string, options?: MicromatchOptions): RegExp;
scan(str: string, options?: MicromatchOptions): string[];
parse(str: string, options?: MicromatchOptions): object;
compile(str: string, options?: MicromatchOptions): (str: string) => boolean;
create(str: string, options?: MicromatchOptions): object;
}
const micromatch: Micromatch;
export = micromatch;
}
```
--------------------------------------------------------------------------------
/tests/unit/services/gemini/GitHubApiService.test.vitest.ts:
--------------------------------------------------------------------------------
```typescript
// Using vitest globals - see vitest.config.ts globals: true
// Mock Octokit and related modules using vi.doMock to avoid hoisting issues
const mockOctokit = {
rest: {
repos: {
getContent: vi.fn(),
get: vi.fn(),
},
pulls: {
get: vi.fn(),
listFiles: vi.fn(),
},
},
};
const mockGraphql = vi.fn() as any;
mockGraphql.defaults = vi.fn().mockReturnValue(mockGraphql);
vi.doMock("@octokit/rest", () => ({
Octokit: vi.fn().mockImplementation(() => mockOctokit),
}));
vi.doMock("@octokit/graphql", () => ({
graphql: mockGraphql,
}));
vi.doMock("@octokit/request-error", () => ({
RequestError: class MockRequestError extends Error {
status: number;
response: any;
constructor(message: string, status: number, response?: any) {
super(message);
this.status = status;
this.response = response;
}
},
}));
vi.doMock("keyv", () => ({
default: vi.fn().mockImplementation(() => ({
get: vi.fn(),
set: vi.fn(),
delete: vi.fn(),
clear: vi.fn(),
})),
}));
describe("GitHubApiService", () => {
let GitHubApiService: any;
let logger: any;
let service: any;
beforeAll(async () => {
// Dynamic imports after mocks are set up
const githubApiModule = await import(
"../../../../src/services/gemini/GitHubApiService.js"
);
GitHubApiService = githubApiModule.GitHubApiService;
const loggerModule = await import("../../../../src/utils/logger.js");
logger = loggerModule.logger;
});
beforeEach(() => {
vi.clearAllMocks();
// Mock logger
vi.spyOn(logger, "info").mockImplementation(vi.fn());
vi.spyOn(logger, "warn").mockImplementation(vi.fn());
vi.spyOn(logger, "error").mockImplementation(vi.fn());
vi.spyOn(logger, "debug").mockImplementation(vi.fn());
service = new GitHubApiService();
});
describe("Constructor", () => {
it("should initialize GitHubApiService", () => {
expect(service).toBeDefined();
expect(service).toBeInstanceOf(GitHubApiService);
});
});
describe("Basic functionality", () => {
it("should have required methods", () => {
expect(typeof service.getFileContent).toBe("function");
expect(typeof service.getRepositoryInfoFromUrl).toBe("function");
expect(typeof service.getPullRequest).toBe("function");
expect(typeof service.getPullRequestFiles).toBe("function");
expect(typeof service.checkRateLimit).toBe("function");
expect(typeof service.listDirectory).toBe("function");
expect(typeof service.getDefaultBranch).toBe("function");
});
});
});
```
--------------------------------------------------------------------------------
/tests/unit/tools/mcpToolsTests.test.vitest.ts:
--------------------------------------------------------------------------------
```typescript
// Using vitest globals - see vitest.config.ts globals: true
/*
* DEPRECATED TEST FILE
*
* This test file was written for the old individual MCP tools:
* - mcpConnectToServerTool
* - mcpListServerToolsTool
* - mcpCallServerTool
* - mcpDisconnectFromServerTool
*
* These tools have been refactored and consolidated into a single mcpClientTool.
*
* TODO: Rewrite these tests to test the new mcpClientTool functionality
* or create separate test files for the new consolidated architecture.
*/
// Import the current tools to be tested
import { writeToFileTool } from "../../../src/tools/writeToFileTool.js";
// Import relevant constants/types for testing
import { TOOL_NAME as WRITE_FILE_TOOL_NAME } from "../../../src/tools/schemas/writeToFileParams.js";
// Mock fs/promises
vi.mock("fs/promises", () => ({
writeFile: vi.fn().mockImplementation(async () => undefined),
access: vi.fn().mockImplementation(async () => undefined),
lstat: vi.fn().mockImplementation(async (_path: string) => ({
isSymbolicLink: () => false,
})),
realpath: vi.fn().mockImplementation(async (path: string) => path),
}));
// Mock FileSecurityService
vi.mock("../../../src/utils/FileSecurityService.js", () => {
return {
FileSecurityService: vi.fn().mockImplementation(() => {
return {
secureWriteFile: vi.fn().mockImplementation(async () => undefined),
validateAndResolvePath: vi
.fn()
.mockImplementation((path: string) => path),
isPathWithinAllowedDirs: vi.fn().mockReturnValue(true),
setAllowedDirectories: vi.fn().mockImplementation(() => undefined),
};
}),
};
});
describe("MCP Tools Tests (Legacy)", () => {
describe("writeToFileTool", () => {
it("should be a function that registers the tool", () => {
expect(typeof writeToFileTool).toBe("function");
});
it("should register a tool with the correct name when called", () => {
const mockServer = {
tool: vi.fn(),
};
writeToFileTool(mockServer as any);
expect(mockServer.tool).toHaveBeenCalledTimes(1);
const [toolName] = mockServer.tool.mock.calls[0];
expect(toolName).toBe(WRITE_FILE_TOOL_NAME);
});
});
// Note: All other MCP tool tests have been disabled because the individual tools
// (mcpConnectToServerTool, mcpListServerToolsTool, mcpCallServerTool, mcpDisconnectFromServerTool)
// have been refactored into a consolidated mcpClientTool.
//
// The new mcpClientTool should be tested separately with tests that reflect
// its consolidated architecture and unified interface.
});
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "mcp-gemini-server",
"version": "0.2.0",
"description": "MCP server providing Google Gemini API integration with inline data processing and content generation capabilities",
"main": "dist/server.js",
"bin": {
"mcp-gemini-server": "dist/server.js"
},
"type": "module",
"scripts": {
"start": "node dist/server.js",
"build": "tsc",
"build:test": "tsc -p tsconfig.test.json",
"dev": "nodemon --watch src --ext ts --exec \"node --loader ts-node/esm src/server.ts\"",
"lint": "eslint . --ext .js,.ts --fix",
"format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
"test": "vitest run",
"test:unit": "vitest run --dir tests/unit",
"test:watch": "vitest watch",
"test:coverage": "vitest run --coverage",
"test:github": "vitest run tests/unit/services/gemini/GitHubApiService.test.ts",
"test:integration": "vitest run --dir tests/integration",
"test:e2e": "vitest run --dir tests/e2e",
"prepare": "husky",
"lint:watch": "nodemon --watch src --watch tests --ext ts,js --exec \"npm run lint -- --fix\"",
"format:watch": "nodemon --watch src --watch tests --ext ts,js --exec \"npm run format\"",
"typecheck": "tsc --noEmit --strict && tsc --noEmit --strict -p tsconfig.test.json",
"check-all": "npm run format && npm run lint && npm run typecheck"
},
"lint-staged": {
"*.{ts,js}": [
"prettier --write",
"eslint --fix"
]
},
"keywords": [
"mcp",
"model-context-protocol"
],
"license": "ISC",
"dependencies": {
"@google/genai": "^0.10.0",
"@modelcontextprotocol/sdk": "^1.11.5",
"@octokit/graphql": "^8.2.2",
"@octokit/rest": "^21.1.1",
"@types/better-sqlite3": "^7.6.13",
"@types/eventsource": "^1.1.15",
"@types/inquirer": "^9.0.7",
"@types/uuid": "^10.0.0",
"better-sqlite3": "^11.10.0",
"chalk": "^5.3.0",
"eventsource": "^2.0.2",
"express": "^5.1.0",
"gitdiff-parser": "^0.3.1",
"inquirer": "^12.5.0",
"keyv": "^5.3.3",
"micromatch": "^4.0.8",
"node-fetch": "^2.6.9",
"uuid": "^11.1.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/cors": "^2.8.18",
"@types/express": "^5.0.1",
"@types/node": "^20.14.2",
"@types/node-fetch": "^2.6.4",
"@typescript-eslint/eslint-plugin": "^7.13.0",
"@typescript-eslint/parser": "^7.13.0",
"@vitest/coverage-v8": "^3.1.4",
"dotenv": "^16.5.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"husky": "^9.1.7",
"lint-staged": "^15.5.2",
"nodemon": "^3.1.3",
"prettier": "^3.3.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"vitest": "^3.1.4"
}
}
```
--------------------------------------------------------------------------------
/src/utils/filePathSecurity.ts:
--------------------------------------------------------------------------------
```typescript
import * as fs from "fs";
import * as path from "path";
import { logger } from "./logger.js";
import { ValidationError } from "./errors.js";
/**
* Validates that a file path is secure and resolves it to an absolute path
*
* Security checks:
* 1. Ensures the path exists
* 2. Ensures the path is within the allowed base directory
* 3. Prevents path traversal attacks
*
* @param filePath - The file path to validate
* @param options - Optional configuration
* @returns The validated absolute file path
* @throws ValidationError if the path is invalid or insecure
*/
export function validateAndResolvePath(
filePath: string,
options: {
mustExist?: boolean;
} = {}
): string {
const { mustExist = true } = options;
// Get the safe base directory from environment variable
const safeBaseDir = process.env.GEMINI_SAFE_FILE_BASE_DIR
? path.normalize(process.env.GEMINI_SAFE_FILE_BASE_DIR)
: path.resolve(process.cwd());
logger.debug(`Validating file path: ${filePath}`);
logger.debug(`Safe base directory: ${safeBaseDir}`);
// Resolve the absolute path
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.resolve(safeBaseDir, filePath);
// Check if the file exists (if required)
if (mustExist && !fs.existsSync(absolutePath)) {
logger.warn(`File not found: ${absolutePath}`);
throw new ValidationError(`File not found: ${absolutePath}`);
}
// Check if the path is within the safe base directory
const normalizedPath = path.normalize(absolutePath);
const relativePath = path.relative(safeBaseDir, normalizedPath);
// Path traversal check - if the relative path starts with '..' or is absolute,
// it's attempting to access files outside the safe directory
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
logger.warn(`Attempted path traversal: ${filePath}`);
throw new ValidationError(
`Access denied: The file path must be within the allowed directory`
);
}
logger.debug(`Validated path: ${normalizedPath}`);
return normalizedPath;
}
/**
* Environment variable configuration for file path security
*/
export function configureFilePathSecurity(): void {
// Add the environment variable to the required vars if using custom base dir
const customBaseDir = process.env.GEMINI_SAFE_FILE_BASE_DIR;
if (customBaseDir) {
// Validate that the custom base directory exists
if (!fs.existsSync(customBaseDir)) {
logger.warn(
`Configured GEMINI_SAFE_FILE_BASE_DIR does not exist: ${customBaseDir}`
);
logger.warn(`Falling back to default directory: ${process.cwd()}`);
} else {
logger.info(`File operations restricted to: ${customBaseDir}`);
}
} else {
logger.info(
`File operations restricted to current working directory: ${process.cwd()}`
);
logger.info(
`Set GEMINI_SAFE_FILE_BASE_DIR environment variable to customize this.`
);
}
}
```
--------------------------------------------------------------------------------
/tests/integration/dummyMcpServerStdio.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
// Import the MCP SDK server module
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create a new MCP server
const server = new McpServer({
name: "dummy-mcp-server-stdio",
version: "1.0.0",
description: "A dummy MCP server for testing stdio transport",
});
// Register an echo tool
server.tool(
"echoTool",
"A tool that echoes back the input message",
{
message: z.string().describe("The message to echo"),
},
async (args: unknown) => {
const typedArgs = args as { message: string };
return {
content: [
{
type: "text",
text: JSON.stringify(
{
message: typedArgs.message,
timestamp: new Date().toISOString(),
},
null,
2
),
},
],
};
}
);
// Register an add tool
server.tool(
"addTool",
"A tool that adds two numbers",
{
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
},
async (args: unknown) => {
const typedArgs = args as { a: number; b: number };
const sum = typedArgs.a + typedArgs.b;
return {
content: [
{
type: "text",
text: JSON.stringify(
{
sum,
inputs: { a: typedArgs.a, b: typedArgs.b },
},
null,
2
),
},
],
};
}
);
// Register a complex data tool that returns a nested JSON structure
server.tool(
"complexDataTool",
"A tool that returns a complex JSON structure",
{
depth: z
.number()
.optional()
.describe("Depth of nested objects to generate"),
itemCount: z
.number()
.optional()
.describe("Number of items to generate in arrays"),
},
async (args: unknown) => {
const typedArgs = args as { depth?: number; itemCount?: number };
const depth = typedArgs.depth || 3;
const itemCount = typedArgs.itemCount || 2;
// Generate a nested structure of specified depth
function generateNestedData(currentDepth: number): any {
if (currentDepth <= 0) {
return { value: "leaf data" };
}
const result = {
level: depth - currentDepth + 1,
timestamp: new Date().toISOString(),
items: [] as any[],
};
for (let i = 0; i < itemCount; i++) {
result.items.push(generateNestedData(currentDepth - 1));
}
return result;
}
const data = generateNestedData(depth);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
);
// Connect a stdio transport
async function startServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
// Log a message to stderr
console.error("Dummy MCP Server (stdio) started");
}
startServer().catch(console.error);
```
--------------------------------------------------------------------------------
/src/tools/geminiGenerateImageParams.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import {
ModelNameSchema,
ModelPreferencesSchema,
} from "./schemas/CommonSchemas.js";
export const TOOL_NAME_GENERATE_IMAGE = "gemini_generate_image";
// Tool Description
export const TOOL_DESCRIPTION_GENERATE_IMAGE = `
Generates images from text prompts using advanced AI models like Imagen 3.1 and Gemini.
This tool takes a text prompt and optional parameters to control the image generation process.
Returns one or more generated images as base64-encoded data with appropriate MIME types.
Supports configurable resolutions, image counts, content safety settings, and style options.
`;
// Zod Schema for image resolution
export const imageResolutionSchema = z
.enum(["512x512", "1024x1024", "1536x1536"])
.describe("The desired resolution of generated images.");
// Style presets available for image generation
export const stylePresetSchema = z
.enum([
"photographic",
"digital-art",
"cinematic",
"anime",
"3d-render",
"oil-painting",
"watercolor",
"pixel-art",
"sketch",
"comic-book",
"neon",
"fantasy",
])
.describe("Style preset to apply to the generated image.");
// Reuse existing safety settings schema from common schemas
import { SafetySettingSchema } from "./schemas/CommonSchemas.js";
// Main parameters schema
export const GEMINI_GENERATE_IMAGE_PARAMS = z.object({
modelName: ModelNameSchema,
prompt: z
.string()
.min(1)
.max(1000)
.describe(
"Required. The text prompt describing the desired image for the model to generate."
),
resolution: imageResolutionSchema
.optional()
.describe(
"Optional. The desired resolution of the generated image(s). Defaults to '1024x1024' if not specified."
),
numberOfImages: z
.number()
.int()
.min(1)
.max(8)
.optional()
.describe(
"Optional. Number of images to generate (1-8). Defaults to 1 if not specified."
),
safetySettings: z
.array(SafetySettingSchema)
.optional()
.describe(
"Optional. A list of safety settings to apply, overriding default model safety settings. Each setting specifies a harm category and a blocking threshold."
),
negativePrompt: z
.string()
.max(1000)
.optional()
.describe(
"Optional. Text description of features to avoid in the generated image(s)."
),
stylePreset: stylePresetSchema
.optional()
.describe(
"Optional. Visual style to apply to the generated image (e.g., 'photographic', 'anime')."
),
seed: z
.number()
.int()
.optional()
.describe(
"Optional. Seed value for reproducible generation. Use the same seed to get similar results."
),
styleStrength: z
.number()
.min(0)
.max(1)
.optional()
.describe(
"Optional. The strength of the style preset (0.0-1.0). Higher values apply more style. Defaults to 0.5."
),
modelPreferences: ModelPreferencesSchema,
});
// Type for parameter object using zod inference
export type GeminiGenerateImageArgs = z.infer<
typeof GEMINI_GENERATE_IMAGE_PARAMS
>;
```
--------------------------------------------------------------------------------
/src/types/modelcontextprotocol-sdk.d.ts:
--------------------------------------------------------------------------------
```typescript
declare module "@modelcontextprotocol/sdk" {
export interface Tool {
(
req: import("express").Request,
res: import("express").Response,
services: Record<string, unknown>
): Promise<void>;
}
}
declare module "@modelcontextprotocol/sdk/types.js" {
export enum ErrorCode {
InvalidParams = "INVALID_PARAMS",
InvalidRequest = "INVALID_REQUEST",
InternalError = "INTERNAL_ERROR",
}
export class McpError extends Error {
constructor(code: ErrorCode, message: string, details?: unknown);
code: ErrorCode;
details?: unknown;
}
export interface CallToolResult {
content: Array<
| {
type: "text";
text: string;
[x: string]: unknown;
}
| {
type: "image";
data: string;
mimeType: string;
[x: string]: unknown;
}
| {
type: "resource";
resource:
| {
text: string;
uri: string;
mimeType?: string;
[x: string]: unknown;
}
| {
uri: string;
blob: string;
mimeType?: string;
[x: string]: unknown;
};
[x: string]: unknown;
}
>;
isError?: boolean;
[x: string]: unknown;
}
}
declare module "@modelcontextprotocol/sdk/server/mcp.js" {
export interface Transport {
start(): Promise<void>;
send(message: unknown): Promise<void>;
close(): Promise<void>;
}
export class McpServer {
constructor(options: {
name: string;
version: string;
description: string;
});
connect(transport: Transport): Promise<void>;
disconnect(): Promise<void>;
registerTool(
name: string,
handler: (args: unknown) => Promise<unknown>,
schema: unknown
): void;
// Add the tool method that's being used in the codebase
tool(
name: string,
description: string,
params: unknown,
handler: (args: unknown) => Promise<unknown>
): void;
}
}
declare module "@modelcontextprotocol/sdk/server/stdio.js" {
import { Transport } from "@modelcontextprotocol/sdk/server/mcp.js";
export class StdioServerTransport implements Transport {
constructor();
start(): Promise<void>;
send(message: unknown): Promise<void>;
close(): Promise<void>;
}
}
declare module "@modelcontextprotocol/sdk/server/streamableHttp.js" {
import { Transport } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { Request, Response } from "express";
export interface StreamableHTTPServerTransportOptions {
sessionIdGenerator?: () => string;
onsessioninitialized?: (sessionId: string) => void;
}
export class StreamableHTTPServerTransport implements Transport {
constructor(options?: StreamableHTTPServerTransportOptions);
start(): Promise<void>;
send(message: unknown): Promise<void>;
close(): Promise<void>;
readonly sessionId?: string;
onclose?: () => void;
handleRequest(req: Request, res: Response, body?: unknown): Promise<void>;
}
}
```
--------------------------------------------------------------------------------
/src/tools/geminiGenerateImageTool.ts:
--------------------------------------------------------------------------------
```typescript
import { GeminiService } from "../services/index.js";
import { logger } from "../utils/index.js";
import {
TOOL_NAME_GENERATE_IMAGE,
TOOL_DESCRIPTION_GENERATE_IMAGE,
GEMINI_GENERATE_IMAGE_PARAMS,
GeminiGenerateImageArgs,
} from "./geminiGenerateImageParams.js";
import { mapAnyErrorToMcpError } from "../utils/errors.js";
import type { NewGeminiServiceToolObject } from "./registration/ToolAdapter.js";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import type { HarmCategory, HarmBlockThreshold } from "@google/genai";
/**
* Handles Gemini image generation operations.
* Generates images from text prompts using Google's image generation models.
*/
export const geminiGenerateImageTool: NewGeminiServiceToolObject<
GeminiGenerateImageArgs,
CallToolResult
> = {
name: TOOL_NAME_GENERATE_IMAGE,
description: TOOL_DESCRIPTION_GENERATE_IMAGE,
inputSchema: GEMINI_GENERATE_IMAGE_PARAMS,
execute: async (args: GeminiGenerateImageArgs, service: GeminiService) => {
logger.debug(`Received ${TOOL_NAME_GENERATE_IMAGE} request:`, {
model: args.modelName,
resolution: args.resolution,
numberOfImages: args.numberOfImages,
}); // Avoid logging full prompt for privacy/security
try {
// Extract arguments and call the service
const {
modelName,
prompt,
resolution,
numberOfImages,
safetySettings,
negativePrompt,
stylePreset,
seed,
styleStrength,
modelPreferences,
} = args;
// Convert safety settings from schema to SDK types if provided
const convertedSafetySettings = safetySettings?.map((setting) => ({
category: setting.category as HarmCategory,
threshold: setting.threshold as HarmBlockThreshold,
}));
const result = await service.generateImage(
prompt,
modelName,
resolution,
numberOfImages,
convertedSafetySettings,
negativePrompt,
stylePreset,
seed,
styleStrength,
modelPreferences?.preferQuality,
modelPreferences?.preferSpeed
);
// Check if images were generated
if (!result.images || result.images.length === 0) {
throw new Error("No images were generated");
}
// Format success output for MCP - provide both JSON and direct image formats
// This allows clients to choose the most appropriate format for their needs
return {
content: [
// Include a text description of the generated images
{
type: "text" as const,
text: `Generated ${result.images.length} ${resolution || "1024x1024"} image(s) from prompt.`,
},
// Include the generated images as image content types
...result.images.map((img) => ({
type: "image" as const,
mimeType: img.mimeType,
data: img.base64Data,
})),
],
};
} catch (error: unknown) {
logger.error(`Error processing ${TOOL_NAME_GENERATE_IMAGE}:`, error);
// Use the centralized error mapping utility to ensure consistent error handling
throw mapAnyErrorToMcpError(error, TOOL_NAME_GENERATE_IMAGE);
}
},
};
```
--------------------------------------------------------------------------------
/tests/utils/mock-types.ts:
--------------------------------------------------------------------------------
```typescript
// Using vitest globals - see vitest.config.ts globals: true
/**
* Mock types for testing
* This file contains type definitions for mocks used in tests
*/
import type { Mock } from "vitest";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpClientService } from "../../src/services/mcp/McpClientService.js";
/**
* Mock Event Source States for testing
*/
export const EVENT_SOURCE_STATES = {
CONNECTING: 0,
OPEN: 1,
CLOSED: 2,
};
/**
* Mock Event for EventSource
*/
export interface MockEvent {
type: string;
data?: string;
message?: string;
error?: Error;
lastEventId?: string;
origin?: string;
bubbles?: boolean;
cancelBubble?: boolean;
cancelable?: boolean;
composed?: boolean;
}
/**
* Mock EventSource for testing
*/
export class MockEventSource {
url: string;
readyState: number = EVENT_SOURCE_STATES.CONNECTING;
onopen: ((event: MockEvent) => void) | null = null;
onmessage: ((event: MockEvent) => void) | null = null;
onerror: ((event: MockEvent) => void) | null = null;
constructor(url: string, _options?: Record<string, unknown>) {
this.url = url;
}
close(): void {
this.readyState = EVENT_SOURCE_STATES.CLOSED;
}
}
/**
* Utility type for mocking a McpClientService
*/
export type MockMcpClientService = {
[K in keyof McpClientService]: McpClientService[K] extends (
...args: unknown[]
) => unknown
? Mock
: McpClientService[K];
};
/**
* Create a mock McpClientService
*/
export function createMockMcpClientService(): MockMcpClientService {
return {
connect: vi.fn(),
listTools: vi.fn(),
callTool: vi.fn(),
disconnect: vi.fn(),
getActiveSseConnectionIds: vi.fn(),
getActiveStdioConnectionIds: vi.fn(),
getLastActivityTimestamp: vi.fn(),
closeSseConnection: vi.fn(),
closeStdioConnection: vi.fn(),
closeAllConnections: vi.fn(),
} as unknown as MockMcpClientService;
}
/**
* Utility type for mocking a FileSecurityService
*/
export type MockFileSecurityService = {
allowedDirectories: string[];
DEFAULT_SAFE_BASE_DIR: string;
setSecureBasePath: Mock;
getSecureBasePath: Mock;
setAllowedDirectories: Mock;
getAllowedDirectories: Mock;
validateAndResolvePath: Mock;
isPathWithinAllowedDirs: Mock;
fullyResolvePath: Mock;
secureWriteFile: Mock;
};
/**
* Create a mock FileSecurityService
*/
export function createMockFileSecurityService(): MockFileSecurityService {
return {
allowedDirectories: ["/test/dir"],
DEFAULT_SAFE_BASE_DIR: "/test/dir",
setSecureBasePath: vi.fn(),
getSecureBasePath: vi.fn(),
setAllowedDirectories: vi.fn(),
getAllowedDirectories: vi.fn(),
validateAndResolvePath: vi.fn(),
isPathWithinAllowedDirs: vi.fn(),
fullyResolvePath: vi.fn(),
secureWriteFile: vi.fn(),
};
}
/**
* Tool handler function type for mcp server
*/
export type ToolHandler = (server: McpServer, service?: unknown) => unknown;
/**
* Utility function to create a mock tool function
*/
export function createMockToolHandler(name: string): ToolHandler {
return vi.fn().mockImplementation((server: McpServer, _service?: unknown) => {
server.tool(name, `Mock ${name}`, {}, vi.fn());
return { name, registered: true };
});
}
```
--------------------------------------------------------------------------------
/src/tools/mcpClientParams.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
export const TOOL_NAME_MCP_CLIENT = "mcp_client";
// Tool Description
export const TOOL_DESCRIPTION_MCP_CLIENT = `
Manages MCP (Model Context Protocol) client connections and operations.
Supports connecting to MCP servers via stdio or SSE transports, disconnecting from servers,
listing available tools on connected servers, and calling tools on those servers.
The operation parameter determines which action to perform.
`;
// Operation type enum
export const mcpOperationSchema = z
.enum([
"connect_stdio",
"connect_sse",
"disconnect",
"list_tools",
"call_tool",
])
.describe("The MCP client operation to perform");
// Connect operation parameters - stdio variant
const connectStdioParams = z.object({
operation: z.literal("connect_stdio"),
transport: z.literal("stdio"),
command: z
.string()
.describe("The command to execute to start the MCP server"),
args: z
.array(z.string())
.optional()
.describe("Arguments to pass to the command"),
clientId: z
.string()
.optional()
.describe("Unique identifier for this client connection"),
connectionToken: z
.string()
.optional()
.describe("Authentication token for secure connections"),
});
// Connect operation parameters - SSE variant
const connectSseParams = z.object({
operation: z.literal("connect_sse"),
transport: z.literal("sse"),
url: z.string().url().describe("The URL of the SSE MCP server"),
clientId: z
.string()
.optional()
.describe("Unique identifier for this client connection"),
connectionToken: z
.string()
.optional()
.describe("Authentication token for secure connections"),
});
// Disconnect operation parameters
const disconnectParams = z.object({
operation: z.literal("disconnect"),
connectionId: z
.string()
.describe("Required. The ID of the connection to close"),
});
// List tools operation parameters
const listToolsParams = z.object({
operation: z.literal("list_tools"),
connectionId: z
.string()
.describe("Required. The ID of the connection to query for tools"),
});
// Call tool operation parameters
const callToolParams = z.object({
operation: z.literal("call_tool"),
connectionId: z
.string()
.describe("Required. The ID of the connection to use"),
toolName: z.string().describe("Required. The name of the tool to call"),
toolParameters: z
.record(z.any())
.optional()
.describe("Parameters to pass to the tool"),
outputFilePath: z
.string()
.optional()
.describe(
"If provided, writes the tool output to this file path instead of returning it"
),
overwriteFile: z
.boolean()
.default(true)
.describe(
"Whether to overwrite the output file if it already exists. Defaults to true."
),
});
// Combined schema using discriminated union
export const MCP_CLIENT_PARAMS = z.discriminatedUnion("operation", [
connectStdioParams,
connectSseParams,
disconnectParams,
listToolsParams,
callToolParams,
]);
// Type for parameter object using zod inference
export type McpClientArgs = z.infer<typeof MCP_CLIENT_PARAMS>;
// Export for use in other modules
export const McpClientParamsModule = {
TOOL_NAME_MCP_CLIENT,
TOOL_DESCRIPTION_MCP_CLIENT,
MCP_CLIENT_PARAMS,
};
```
--------------------------------------------------------------------------------
/tests/unit/utils/healthCheck.test.vitest.ts:
--------------------------------------------------------------------------------
```typescript
// Using vitest globals - see vitest.config.ts globals: true
import http from "node:http";
import {
getHealthStatus,
setServerState,
startHealthCheckServer,
ServerState,
} from "../../../src/utils/healthCheck.js";
describe("Health Check", () => {
let healthServer: http.Server;
const testPort = 3333; // Use a specific port for tests
// Store the original environment variable value
const originalHealthCheckPort = process.env.HEALTH_CHECK_PORT;
// Mock server state
const mockServerState: ServerState = {
isRunning: true,
startTime: Date.now() - 5000, // 5 seconds ago
transport: null, // Transport interface doesn't have constructor property
server: {},
healthCheckServer: null,
mcpClientService: null,
};
// Setup: Start health check server
it("should initialize health check server", () => {
setServerState(mockServerState);
// Set the port via environment variable for our test
process.env.HEALTH_CHECK_PORT = testPort.toString();
healthServer = startHealthCheckServer();
expect(healthServer).toBeTruthy();
// Wait briefly for the server to start listening
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, 100);
});
});
// Test health status function
it("should return correct health status", () => {
const status = getHealthStatus();
expect(status.status).toBe("running");
expect(status.uptime).toBeGreaterThanOrEqual(5);
expect(status.transport).toBe("MockTransport");
});
// Test health endpoint
it("should respond to health endpoint", async () => {
// Make HTTP request to health endpoint
const response = await new Promise<http.IncomingMessage>((resolve) => {
const req = http.request(
{
hostname: "localhost",
port: testPort,
path: "/health",
method: "GET",
},
(res) => {
resolve(res);
}
);
req.end();
});
// Check response status
expect(response.statusCode).toBe(200);
// Check response content
const data = await new Promise<string>((resolve) => {
let body = "";
response.on("data", (chunk) => {
body += chunk;
});
response.on("end", () => {
resolve(body);
});
});
const healthData = JSON.parse(data);
expect(healthData.status).toBe("running");
expect(healthData.uptime).toBeGreaterThanOrEqual(0);
expect(healthData.transport).toBe("MockTransport");
});
// Test 404 for unknown paths
it("should return 404 for unknown paths", async () => {
// Make HTTP request to unknown path
const response = await new Promise<http.IncomingMessage>((resolve) => {
const req = http.request(
{
hostname: "localhost",
port: testPort,
path: "/unknown",
method: "GET",
},
(res) => {
resolve(res);
}
);
req.end();
});
// Check response status
expect(response.statusCode).toBe(404);
});
// Cleanup: Close server after tests
afterAll(() => {
if (healthServer) {
healthServer.close();
}
// Restore the environment variable or delete it if it wasn't set before
if (originalHealthCheckPort === undefined) {
delete process.env.HEALTH_CHECK_PORT;
} else {
process.env.HEALTH_CHECK_PORT = originalHealthCheckPort;
}
});
});
```
--------------------------------------------------------------------------------
/src/tools/registration/ToolRegistry.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tool Registry - Central management for MCP tools
*
* This file introduces a more consistent approach to tool registration
* that provides better type safety and simpler maintenance.
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { logger } from "../../utils/logger.js";
import { GeminiService } from "../../services/GeminiService.js";
import { McpClientService } from "../../services/mcp/McpClientService.js";
import { z } from "zod";
/**
* Interface for tool registration function - base type
*/
export interface ToolRegistration {
/**
* Registers a tool with the MCP server
* @param server The MCP server instance
* @param services Container with available services
*/
registerTool(server: McpServer, services: ServiceContainer): void;
}
/**
* Container with services available for tools
*/
export interface ServiceContainer {
geminiService: GeminiService;
mcpClientService: McpClientService;
}
/**
* Tool registration function type - for standalone functions
*/
export type ToolRegistrationFn = (
server: McpServer,
services: ServiceContainer
) => void;
/**
* Tool factory that creates a simple tool without parameter validation
*/
export function createBasicTool(
name: string,
description: string,
handler: (args: unknown) => Promise<unknown>
): ToolRegistrationFn {
return (server: McpServer, _services: ServiceContainer) => {
server.tool(name, description, {}, handler);
logger.info(`Basic tool registered: ${name}`);
};
}
/**
* Tool factory that creates a fully-validated tool with Zod schema
*/
export function createValidatedTool<T extends z.ZodRawShape, R>(
name: string,
description: string,
params: T,
handler: (args: z.infer<z.ZodObject<T>>) => Promise<R>
): ToolRegistrationFn {
return (server: McpServer, _services: ServiceContainer) => {
// Create a wrapper with proper type inference
const wrappedHandler = async (args: z.infer<z.ZodObject<T>>) => {
return handler(args);
};
server.tool(
name,
description,
params,
wrappedHandler as (args: unknown) => Promise<unknown>
);
logger.info(`Validated tool registered: ${name}`);
};
}
/**
* Registry that manages tool registration
*/
export class ToolRegistry {
private toolRegistrations: ToolRegistrationFn[] = [];
private services: ServiceContainer;
/**
* Creates a new tool registry
* @param geminiService GeminiService instance
* @param mcpClientService McpClientService instance
*/
constructor(
geminiService: GeminiService,
mcpClientService: McpClientService
) {
this.services = {
geminiService,
mcpClientService,
};
}
/**
* Adds a tool to the registry
* @param registration Tool registration function
*/
public registerTool(registration: ToolRegistrationFn): void {
this.toolRegistrations.push(registration);
}
/**
* Registers all tools with the MCP server
* @param server McpServer instance
*/
public registerAllTools(server: McpServer): void {
logger.info(`Registering ${this.toolRegistrations.length} tools...`);
for (const registration of this.toolRegistrations) {
try {
registration(server, this.services);
} catch (error) {
logger.error(
`Failed to register tool: ${error instanceof Error ? error.message : String(error)}`
);
}
}
logger.info("All tools registered successfully");
}
}
```
--------------------------------------------------------------------------------
/src/types/googleGenAITypes.ts:
--------------------------------------------------------------------------------
```typescript
// Import types directly from the SDK
import type {
GenerateContentResponse,
GenerationConfig as GoogleGenerationConfig,
SafetySetting,
Content,
Tool,
ToolConfig,
} from "@google/genai";
// Define ThinkingConfig interface for controlling model reasoning
interface ThinkingConfig {
thinkingBudget?: number;
reasoningEffort?: "none" | "low" | "medium" | "high";
}
// Override the imported GenerationConfig with our extended version
interface GenerationConfig extends GoogleGenerationConfig {
thinkingConfig?: ThinkingConfig;
thinkingBudget?: number;
}
// Define ExtendedGenerationConfig (alias for our GenerationConfig)
type ExtendedGenerationConfig = GenerationConfig;
// Types for params that match the SDK v0.10.0 structure
interface ChatSessionParams {
history?: Content[];
generationConfig?: GenerationConfig;
safetySettings?: SafetySetting[];
tools?: Tool[];
thinkingConfig?: ThinkingConfig;
systemInstruction?: Content;
cachedContent?: string;
}
interface CachedContentParams {
contents: Content[];
displayName?: string;
systemInstruction?: Content;
ttl?: string;
tools?: Tool[];
toolConfig?: ToolConfig;
}
// Metadata returned by cached content operations
interface CachedContentMetadata {
name: string;
displayName?: string;
model?: string;
createTime: string;
updateTime: string;
expirationTime?: string;
state?: string;
usageMetadata?: {
totalTokenCount?: number;
};
}
// Enums for response types - matching SDK v0.10.0
enum FinishReason {
FINISH_REASON_UNSPECIFIED = "FINISH_REASON_UNSPECIFIED",
STOP = "STOP",
MAX_TOKENS = "MAX_TOKENS",
SAFETY = "SAFETY",
RECITATION = "RECITATION",
OTHER = "OTHER",
}
enum BlockedReason {
BLOCKED_REASON_UNSPECIFIED = "BLOCKED_REASON_UNSPECIFIED",
SAFETY = "SAFETY",
OTHER = "OTHER",
}
// Define our own LocalFunctionCall interface to avoid conflict with imported FunctionCall
interface LocalFunctionCall {
name: string;
args?: Record<string, unknown>;
}
// Response type interfaces
interface PromptFeedback {
blockReason?: BlockedReason;
safetyRatings?: Array<{
category: string;
probability: string;
blocked: boolean;
}>;
}
interface Candidate {
content?: Content;
finishReason?: FinishReason;
safetyRatings?: Array<{
category: string;
probability: string;
blocked: boolean;
}>;
index?: number;
}
// Interface for the chat session with our updated implementation
interface ChatSession {
model: string;
config: {
history?: Content[];
generationConfig?: GenerationConfig;
safetySettings?: SafetySetting[];
tools?: Tool[];
systemInstruction?: Content;
cachedContent?: string;
thinkingConfig?: ThinkingConfig;
};
history: Content[];
}
// These are already defined above, so we don't need to redefine them
// Using the existing PromptFeedback and Candidate interfaces
interface GenerateContentResult {
response: {
text(): string;
promptFeedback?: PromptFeedback;
candidates?: Candidate[];
};
}
interface GenerateContentResponseChunk {
text(): string;
candidates?: Candidate[];
}
// Re-export all types for use in other files
export type {
ChatSessionParams,
CachedContentParams,
CachedContentMetadata,
GenerateContentResult,
GenerateContentResponseChunk,
PromptFeedback,
Candidate,
LocalFunctionCall as FunctionCall,
ChatSession,
GenerateContentResponse,
ThinkingConfig,
GenerationConfig,
ExtendedGenerationConfig,
GoogleGenerationConfig,
};
export { FinishReason, BlockedReason };
```
--------------------------------------------------------------------------------
/tests/utils/express-mocks.ts:
--------------------------------------------------------------------------------
```typescript
import { Request, Response } from "express";
import { ParamsDictionary } from "express-serve-static-core";
import { ParsedQs } from "qs";
/**
* Creates a mock Express Request object for testing
*
* @param options Object containing request properties to mock
* @returns A mock Express Request object
*/
export function createMockRequest<
P = ParamsDictionary,
ResBody = any,
ReqBody = any,
ReqQuery = ParsedQs,
>(
options: Partial<Request<P, ResBody, ReqBody, ReqQuery>> = {}
): Request<P, ResBody, ReqBody, ReqQuery> {
// Create a base mock request with common methods and properties
const mockRequest = {
app: {},
baseUrl: "",
body: {},
cookies: {},
fresh: false,
hostname: "localhost",
ip: "127.0.0.1",
ips: [],
method: "GET",
originalUrl: "",
params: {},
path: "/",
protocol: "http",
query: {},
route: {},
secure: false,
signedCookies: {},
stale: true,
subdomains: [],
xhr: false,
accepts: () => [],
acceptsCharsets: () => [],
acceptsEncodings: () => [],
acceptsLanguages: () => [],
get: () => "",
header: () => "",
is: () => false,
range: () => [],
...options,
} as Request<P, ResBody, ReqBody, ReqQuery>;
return mockRequest;
}
/**
* Creates a mock Express Response object for testing
*
* @param options Object containing response properties to mock
* @returns A mock Express Response object
*/
export function createMockResponse<ResBody = any>(
options: Partial<Response<ResBody>> = {}
): Response<ResBody> {
// Create response behaviors
let statusCode = 200;
let responseData: unknown = {};
let responseHeaders: Record<string, string> = {};
let endCalled = false;
// Create a base mock response with common methods that satisfies the Express Response interface
const mockResponse = {
app: {},
headersSent: false,
locals: {},
statusCode,
// Response chainable methods
status: function (code: number): Response<ResBody> {
statusCode = code;
return this as Response<ResBody>;
},
sendStatus: function (code: number): Response<ResBody> {
statusCode = code;
return this as Response<ResBody>;
},
json: function (data: unknown): Response<ResBody> {
responseData = data;
return this as Response<ResBody>;
},
send: function (data: unknown): Response<ResBody> {
responseData = data;
return this as Response<ResBody>;
},
end: function (data?: unknown): Response<ResBody> {
if (data) responseData = data;
endCalled = true;
return this as Response<ResBody>;
},
set: function (
field: string | Record<string, string>,
value?: string
): Response<ResBody> {
if (typeof field === "string") {
responseHeaders[field] = value as string;
} else {
responseHeaders = { ...responseHeaders, ...field };
}
return this as Response<ResBody>;
},
get: function (field: string): string | undefined {
return responseHeaders[field];
},
// Testing helpers
_getStatus: function () {
return statusCode;
},
_getData: function () {
return responseData;
},
_getHeaders: function () {
return responseHeaders;
},
_isEnded: function () {
return endCalled;
},
...options,
} as Response<ResBody>;
return mockResponse;
}
// Export mock types for easier consumption
export type MockRequest = ReturnType<typeof createMockRequest>;
export type MockResponse = ReturnType<typeof createMockResponse>;
```
--------------------------------------------------------------------------------
/src/types/googleGenAI.d.ts:
--------------------------------------------------------------------------------
```typescript
// Type augmentation for @google/genai package
import type {
GenerationConfig,
Content,
Part,
Tool,
ToolConfig,
} from "@google/genai";
// Add additional types from our codebase
import type { ExtendedGenerationConfig } from "./googleGenAITypes.js";
declare module "@google/genai" {
// Define Models interface
export interface Models {
getGenerativeModel(params: {
model: string;
generationConfig?: GenerationConfig;
safetySettings?: SafetySetting[];
}): GenerativeModel;
}
// Extend GoogleGenAI class with missing methods
export interface GoogleGenAI {
/**
* Returns a generative model instance with the specified configuration
*
* @param options Model configuration options
* @returns A generative model instance
*/
getGenerativeModel(options: {
model: string;
generationConfig?: GenerationConfig | ExtendedGenerationConfig;
safetySettings?: SafetySetting[];
}): GenerativeModel;
/**
* Returns models available through the API
*/
readonly models: Models;
}
// Image generation related types
export interface ImagePart extends Part {
inlineData: {
data: string;
mimeType: string;
};
}
// Safety setting types are already defined in @google/genai package
// We just need to re-export them from the module declaration
export {
HarmCategory,
HarmBlockThreshold,
SafetySetting,
} from "@google/genai";
// Define the GenerativeModel interface
export interface GenerativeModel {
/**
* Generates content based on provided prompt
*
* @param options Content generation options
* @returns Promise with generated content response
*/
generateContent(options: {
contents: Content[];
generationConfig?: GenerationConfig | ExtendedGenerationConfig;
safetySettings?: SafetySetting[];
tools?: Tool[];
toolConfig?: ToolConfig;
}): Promise<{
response: {
text(): string;
candidates?: Array<{ content?: { parts?: Part[] } }>;
};
}>;
/**
* Generates content as a stream based on provided prompt
*
* @param options Content generation options
* @returns Promise with stream of content responses
*/
generateContentStream(options: {
contents: Content[];
generationConfig?: GenerationConfig | ExtendedGenerationConfig;
safetySettings?: SafetySetting[];
tools?: Tool[];
toolConfig?: ToolConfig;
}): Promise<{
stream: AsyncGenerator<{
text(): string;
candidates?: Array<{ content?: { parts?: Part[] } }>;
}>;
}>;
/**
* Creates a chat session with the model
*/
startChat(options?: {
history?: Content[];
generationConfig?: GenerationConfig | ExtendedGenerationConfig;
safetySettings?: SafetySetting[];
tools?: Tool[];
systemInstruction?: Content;
cachedContent?: string;
thinkingConfig?: { reasoningEffort?: string; thinkingBudget?: number };
}): ChatSession;
/**
* Generates images based on a text prompt
*/
generateImages(params: {
prompt: string;
safetySettings?: SafetySetting[];
[key: string]: unknown;
}): Promise<{
images?: Array<{ data?: string; mimeType?: string }>;
promptSafetyMetadata?: {
blocked?: boolean;
safetyRatings?: Array<{ category: string; probability: string }>;
};
}>;
}
// Define ChatSession interface
export interface ChatSession {
sendMessage(text: string): Promise<{ response: { text(): string } }>;
sendMessageStream(
text: string
): Promise<{ stream: AsyncGenerator<{ text(): string }> }>;
getHistory(): Content[];
}
// We can add specific Google GenAI types if needed
}
```
--------------------------------------------------------------------------------
/tests/utils/env-check.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Environment variable verification for tests
*
* This module is used at the beginning of test runs to verify that
* required environment variables are available and to load them from
* .env.test if needed.
*/
import {
loadTestEnv,
verifyEnvVars,
REQUIRED_ENV_VARS,
createEnvExample,
} from "./environment.js";
/**
* Setup function to be called at the beginning of test runs
* to ensure environment variables are properly loaded
*
* @returns Promise resolving to a boolean indicating if environment is valid
*/
export async function setupTestEnvironment(): Promise<boolean> {
// Try to load variables from .env.test file
await loadTestEnv();
// Check if required variables are available
const basicCheck = verifyEnvVars(REQUIRED_ENV_VARS.BASIC);
if (!basicCheck.success) {
console.error("❌ Missing required environment variables for tests:");
console.error(` ${basicCheck.missing.join(", ")}`);
console.error("\nTests requiring API access will be skipped.");
console.error("To fix this:");
console.error("1. Create a .env.test file in the project root");
console.error("2. Add the missing variables with their values");
// Create an example file to help users
await createEnvExample();
console.error("\n.env.test.example file created for reference\n");
return false;
}
// Check which test categories can run
const fileCheck = verifyEnvVars(REQUIRED_ENV_VARS.FILE_TESTS);
const imageCheck = verifyEnvVars(REQUIRED_ENV_VARS.IMAGE_TESTS);
console.log("✅ Basic API environment variables available");
if (!fileCheck.success) {
console.warn("⚠️ Missing some file API environment variables:");
console.warn(` ${fileCheck.missing.join(", ")}`);
console.warn(" File API tests may be skipped");
} else {
console.log("✅ File API environment variables available");
}
if (!imageCheck.success) {
console.warn("⚠️ Missing some image API environment variables:");
console.warn(` ${imageCheck.missing.join(", ")}`);
console.warn(" Default values will be used for missing variables");
} else {
console.log("✅ Image API environment variables available");
}
return true;
}
/**
* Add a pre-check function to specific test files to skip tests
* if required environment variables are missing
*
* Usage (at the beginning of a test file):
*
* import { describe, it, before } from 'node:test';
* import { preCheckEnv, skipIfEnvMissing } from '../utils/env-check.js';
*
* // Check environment at the start of the file
* const envOk = preCheckEnv(REQUIRED_ENV_VARS.IMAGE_TESTS);
*
* describe('Image generation tests', () => {
* // Skip all tests if environment is not set up
* if (!envOk) return;
*
* // Or check in each test:
* it('should generate an image', (t) => {
* if (skipIfEnvMissing(t, REQUIRED_ENV_VARS.IMAGE_TESTS)) return;
* // ... test code ...
* });
* });
*
* @param requiredVars - Array of required environment variable names
* @returns Boolean indicating if environment is valid for these tests
*/
export function preCheckEnv(
requiredVars: string[] = REQUIRED_ENV_VARS.BASIC
): boolean {
const check = verifyEnvVars(requiredVars);
if (!check.success) {
console.warn(
`⚠️ Skipping tests - missing required environment variables: ${check.missing.join(", ")}`
);
return false;
}
return true;
}
/**
* Skip a test if required environment variables are missing
*
* @param t - Test context from node:test
* @param requiredVars - Array of required environment variable names
* @returns Boolean indicating if the test should be skipped
*/
export function skipIfEnvMissing(
t: { skip: (reason: string) => void },
requiredVars: string[] = REQUIRED_ENV_VARS.BASIC
): boolean {
const check = verifyEnvVars(requiredVars);
if (!check.success) {
t.skip(`Test requires environment variables: ${check.missing.join(", ")}`);
return true;
}
return false;
}
```
--------------------------------------------------------------------------------
/tests/integration/dummyMcpServerSse.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
// Import the MCP SDK server module
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { z } from "zod";
import express, { Request, Response } from "express";
import cors from "cors";
import { Server } from "http";
// Create a new MCP server
const server = new McpServer({
name: "dummy-mcp-server-sse",
version: "1.0.0",
description: "A dummy MCP server for testing SSE transport",
});
// Register the same tools as in the stdio version
server.tool(
"echoTool",
"A tool that echoes back the input message",
{
message: z.string().describe("The message to echo"),
},
async (args: unknown) => {
const typedArgs = args as { message: string };
return {
content: [
{
type: "text",
text: JSON.stringify(
{
message: typedArgs.message,
timestamp: new Date().toISOString(),
},
null,
2
),
},
],
};
}
);
server.tool(
"addTool",
"A tool that adds two numbers",
{
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
},
async (args: unknown) => {
const typedArgs = args as { a: number; b: number };
const sum = typedArgs.a + typedArgs.b;
return {
content: [
{
type: "text",
text: JSON.stringify(
{
sum,
inputs: { a: typedArgs.a, b: typedArgs.b },
},
null,
2
),
},
],
};
}
);
server.tool(
"complexDataTool",
"A tool that returns a complex JSON structure",
{
depth: z
.number()
.optional()
.describe("Depth of nested objects to generate"),
itemCount: z
.number()
.optional()
.describe("Number of items to generate in arrays"),
},
async (args: unknown) => {
const typedArgs = args as { depth?: number; itemCount?: number };
const depth = typedArgs.depth || 3;
const itemCount = typedArgs.itemCount || 2;
// Generate a nested structure of specified depth
function generateNestedData(currentDepth: number): any {
if (currentDepth <= 0) {
return { value: "leaf data" };
}
const result = {
level: depth - currentDepth + 1,
timestamp: new Date().toISOString(),
items: [] as any[],
};
for (let i = 0; i < itemCount; i++) {
result.items.push(generateNestedData(currentDepth - 1));
}
return result;
}
const data = generateNestedData(depth);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
);
// Create Express app and add CORS middleware
const app = express();
app.use(cors());
app.use(express.json());
// Get port from command line argument or environment or default to 3456
const port = Number(process.argv[2]) || Number(process.env.PORT) || 3456;
// Create HTTP server
const httpServer: Server = app.listen(port, () => {
console.error(`Dummy MCP Server (SSE) started on port ${port}`);
});
// Set up SSE endpoint
app.get("/mcp", async (_req: Request, res: Response) => {
// Create SSE transport for this connection
const transport = new SSEServerTransport("/mcp", res);
await transport.start();
// Connect to MCP server
await server.connect(transport);
});
// Set up POST endpoint for receiving messages
app.post("/mcp", async (_req: Request, res: Response) => {
try {
// The SSE transport expects messages to be posted here
// but we need to handle this in the context of an active SSE connection
res.status(200).json({ status: "ok" });
} catch (error: unknown) {
console.error("Error handling POST request:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
res.status(500).json({ error: errorMessage });
}
});
// Add a handler for Ctrl+C to properly shut down the server
process.on("SIGINT", () => {
console.error("Shutting down Dummy MCP Server (SSE)...");
httpServer.close(() => {
process.exit(0);
});
});
```
--------------------------------------------------------------------------------
/src/tools/geminiCodeReviewParams.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import { ModelNameSchema } from "./schemas/CommonSchemas.js";
export const TOOL_NAME_CODE_REVIEW = "gemini_code_review";
// Tool Description
export const TOOL_DESCRIPTION_CODE_REVIEW = `
Performs comprehensive code reviews using Gemini models. Supports reviewing local git diffs,
GitHub repositories, and GitHub pull requests. The source parameter determines which type
of review to perform and which additional parameters are required.
`;
// Review source enum
export const reviewSourceSchema = z
.enum(["local_diff", "github_repo", "github_pr"])
.describe("The source of code to review");
// Common review focus areas schema
export const ReviewFocusSchema = z
.enum(["security", "performance", "architecture", "bugs", "general"])
.optional()
.describe(
"The primary focus area for the review. If not specified, a general comprehensive review will be performed."
);
// Common reasoning effort schema
export const ReasoningEffortSchema = z
.enum(["low", "medium", "high"])
.describe(
"The amount of reasoning effort to apply. Higher effort may produce more detailed analysis."
);
// Base parameters common to all review types
const baseParams = {
source: reviewSourceSchema,
model: ModelNameSchema.optional().describe(
"Optional. The Gemini model to use for review. Defaults based on source type."
),
reasoningEffort: ReasoningEffortSchema.optional(),
reviewFocus: ReviewFocusSchema,
excludePatterns: z
.array(z.string())
.optional()
.describe(
"File patterns to exclude from the review (e.g., ['*.test.ts', 'dist/**'])"
),
customPrompt: z
.string()
.optional()
.describe(
"Additional instructions or context to include in the review prompt"
),
};
// Local diff specific parameters
const localDiffParams = z.object({
...baseParams,
source: z.literal("local_diff"),
diffContent: z
.string()
.describe(
"Required. The git diff content to review (output of 'git diff' or similar)"
),
repositoryContext: z
.object({
name: z.string().optional(),
description: z.string().optional(),
languages: z.array(z.string()).optional(),
frameworks: z.array(z.string()).optional(),
})
.optional()
.describe(
"Optional context about the repository to improve review quality"
),
maxFilesToInclude: z
.number()
.int()
.positive()
.optional()
.describe(
"Maximum number of files to include in the review. Helps manage large diffs."
),
prioritizeFiles: z
.array(z.string())
.optional()
.describe(
"File patterns to prioritize in the review (e.g., ['src/**/*.ts'])"
),
});
// GitHub repository specific parameters
const githubRepoParams = z.object({
...baseParams,
source: z.literal("github_repo"),
repoUrl: z
.string()
.url()
.describe(
"Required. The GitHub repository URL (e.g., 'https://github.com/owner/repo')"
),
branch: z
.string()
.optional()
.describe(
"The branch to review. Defaults to the repository's default branch."
),
maxFiles: z
.number()
.int()
.positive()
.default(100)
.describe("Maximum number of files to review. Defaults to 100."),
prioritizeFiles: z
.array(z.string())
.optional()
.describe(
"File patterns to prioritize in the review (e.g., ['src/**/*.ts'])"
),
});
// GitHub PR specific parameters
const githubPrParams = z.object({
...baseParams,
source: z.literal("github_pr"),
prUrl: z
.string()
.url()
.describe(
"Required. The GitHub pull request URL (e.g., 'https://github.com/owner/repo/pull/123')"
),
filesOnly: z
.boolean()
.optional()
.describe(
"Deprecated. Review only the changed files without considering PR context. Use for backwards compatibility."
),
});
// Combined schema using discriminated union
export const GEMINI_CODE_REVIEW_PARAMS = z.discriminatedUnion("source", [
localDiffParams,
githubRepoParams,
githubPrParams,
]);
// Type for parameter object using zod inference
export type GeminiCodeReviewArgs = z.infer<typeof GEMINI_CODE_REVIEW_PARAMS>;
// Export for use in other modules
export const GeminiCodeReviewParamsModule = {
TOOL_NAME_CODE_REVIEW,
TOOL_DESCRIPTION_CODE_REVIEW,
GEMINI_CODE_REVIEW_PARAMS,
};
```