Skip to content

Repository files navigation

OmniSerp Multi-Search Client and MCP Server

Go CIGo LintGo SASTDocsDocsVisualizationLicense

A modular, plugin-based search engine abstraction package for Go that provides a unified interface for multiple search engines.

Overview

The omniserp package provides:

  • 📦 Unified Client SDK: Single API that fronts multiple search engine backends (client/client.go)
  • 📐 Normalized Responses: Optional unified response structures across all engines (engine-agnostic)
  • Capability Checking: Automatic validation of operation support across different backends
  • 🔌 Unified Interface: Common Engine interface for all search providers
  • 🧩 Plugin Architecture: Easy addition of new search engines
  • 🤝 Multiple Providers: Built-in support for Serper, SerpAPI, Brave Search, and Exa.ai
  • 🔒 Type Safety: Structured parameter and result types
  • 📋 Registry System: Automatic discovery and management of engines
  • 🤖 MCP Server: Model Context Protocol server for AI integration with optional secure credentials (cmd/mcp-omniserp)
  • ⌨️ CLI Tool: Command-line interface for quick searches (cmd/omniserp)

Quick Start

package main
import (
"context""fmt""log""github.com/plexusone/omniserp""github.com/plexusone/omniserp/client"
)
funcmain() {
// Set API key// export SERPER_API_KEY="your_key"// Create client (auto-selects engine)c, err:=client.New()
iferr!=nil {
log.Fatal(err)
}
// Perform a searchresult, err:=c.Search(context.Background(), omniserp.SearchParams{
Query: "golang programming",
})
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("Results: %+v\n", result.Data)
}

Project Structure

omniserp/
├── client/ # Search engine client implementations
│ ├── client.go # Unified Client SDK with capability checking
│ ├── serper/ # Serper.dev implementation
│ ├── serpapi/ # SerpAPI implementation
│ ├── brave/ # Brave Search API
│ └── exa/ # Exa.ai neural search
├── cmd/ # Executable applications
│ ├── mcp-omniserp/ # MCP server for AI integration (with optional secure credentials)
│ └── omniserp/ # CLI tool
├── examples/ # Example programs
│ ├── capability_check/ # Capability checking demo
│ └── normalized_search/ # Normalized responses demo
├── types.go # Core types and Engine interface
├── normalized.go # Normalized response types
├── normalizer.go # Response normalizer
├── omniserp.go # Utility functions
└── README.md

Applications

CLI Tool

Installation

go build ./cmd/omniserp

Basic Usage

# Set API keyexport SERPER_API_KEY="your_api_key"# Basic search (specify engine and query)
./omniserp -e serper -q "golang programming"# Or use long flags
./omniserp --engine serpapi --query "golang programming"# With SerpAPIexport SERPAPI_API_KEY="your_api_key"
./omniserp -e serpapi -q "golang programming"

MCP Server

The Model Context Protocol (MCP) server enables AI assistants to perform web searches through this package.

Installation

go install github.com/plexusone/omniserp/cmd/mcp-omniserp@latest

Or build from source:

go build ./cmd/mcp-omniserp

Configuration

Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

{
"mcpServers": {
"omniserp": {
"command": "mcp-omniserp",
"env": {
"SERPER_API_KEY": "your_serper_api_key",
"SEARCH_ENGINE": "serper"
}
}
}
}

Features

The MCP server dynamically registers only the tools supported by the current search engine backend. This means:

  • When using Serper, all 12 tools are available including Lens search
  • When using SerpAPI, 11 tools are available (Lens is excluded)

Available tool categories:

  • Web Search: General web searches with customizable parameters
  • News Search: Search news articles
  • Image Search: Search for images
  • Video Search: Search for videos
  • Places Search: Search for locations and businesses
  • Maps Search: Search maps data
  • Reviews Search: Search reviews
  • Shopping Search: Search shopping/product listings
  • Scholar Search: Search academic papers
  • Lens Search: Visual search capabilities (Serper only)
  • Autocomplete: Get search suggestions
  • Webpage Scrape: Extract content from webpages

All searches support parameters like location, language, country, and number of results.

Server Logs: The MCP server logs which tools were registered and which were skipped:

2025/12/13 19:00:00 Using engine: serpapi v1.0.0
2025/12/13 19:00:00 Registered 11 tools: [google_search, google_search_news, ...]
2025/12/13 19:00:00 Skipped 1 unsupported tools: [google_search_lens]

Secure Mode (Optional)

The MCP server supports optional secure credential management using VaultGuard. When a policy file exists, API keys are retrieved from the OS keychain instead of environment variables.

Setup for Secure Mode:

  1. Store your API key in the keychain:

    security add-generic-password -s "omnivault" -a "SERPER_API_KEY" -w "your-key"
  2. Create a security policy (~/.vaultguard/policy.json):

    {
    "version": 1,
    "local": {
    "require_encryption": true,
    "min_security_score": 50
    }
    }
  3. Update your Claude Desktop config (no env section needed):

    {
    "mcpServers": {
    "omniserp": {
    "command": "mcp-omniserp"
    }
    }
    }

Note: Without a policy file, the server works exactly as before using environment variables.

Client SDK

The client package provides a high-level SDK that simplifies working with multiple search engines:

Key Features

  • Auto-registration: Automatically discovers and registers all available engines
  • Smart selection: Uses SEARCH_ENGINE environment variable or defaults to Serper
  • Runtime switching: Switch between engines without recreating the client
  • Capability checking: Validates operations before calling backends
  • Error handling: Returns ErrOperationNotSupported for unsupported operations
  • Clean API: Implements the same Engine interface, proxying to the selected backend

Quick Start

import"github.com/plexusone/omniserp/client"// Create client - auto-selects engine based on SEARCH_ENGINE env varc, err:=client.New()
// Or specify engine explicitlyc, err:=client.NewWithEngine("serper")
// Check support before callingifc.SupportsOperation(client.OpSearchLens) {
result, _:=c.SearchLens(ctx, params)
}
// Switch engines at runtimec.SetEngine("serpapi")

Operation Constants

The SDK provides constants for all operations:

  • client.OpSearch - Web search
  • client.OpSearchNews - News search
  • client.OpSearchImages - Image search
  • client.OpSearchVideos - Video search
  • client.OpSearchPlaces - Places search
  • client.OpSearchMaps - Maps search
  • client.OpSearchReviews - Reviews search
  • client.OpSearchShopping - Shopping search
  • client.OpSearchScholar - Scholar search
  • client.OpSearchLens - Lens search (Serper only)
  • client.OpSearchAutocomplete - Autocomplete
  • client.OpScrapeWebpage - Webpage scraping

Normalized Responses

The client SDK provides optional normalized response methods that return unified structures across all search engines:

// Use *Normalized() methods for engine-agnostic response structuresnormalized, err:=c.SearchNormalized(ctx, params)
// Access results in a consistent format regardless of enginefor_, result:=rangenormalized.OrganicResults {
fmt.Printf("%s: %s\n", result.Title, result.Link)
}
// Switch engines without changing your code!c.SetEngine("serpapi")
normalized, err=c.SearchNormalized(ctx, params) // Same structure!

Available Normalized Methods:

  • SearchNormalized() - Web search with normalized results
  • SearchNewsNormalized() - News search with normalized results
  • SearchImagesNormalized() - Image search with normalized results

Benefits:

  • Engine-Agnostic: Same code works with any backend
  • Type-Safe: Strongly-typed result structures
  • Optional: Raw responses still available via standard methods
  • Complete: Preserves original response in Raw field

Example Normalized Structure:

typeNormalizedSearchResultstruct {
OrganicResults []OrganicResult// Standard search resultsAnswerBox*AnswerBox// Featured answerKnowledgeGraph*KnowledgeGraph// Knowledge panelRelatedSearches []RelatedSearch// Related queriesPeopleAlsoAsk []PeopleAlsoAsk// PAA questionsNewsResults []NewsResult// News articlesImageResults []ImageResult// ImagesSearchMetadataSearchMetadata// Search infoRaw*SearchResult// Original response
}

Comparison: Raw vs Normalized

AspectRaw ResponseNormalized Response
Field namesEngine-specificUnified
StructureVaries by engineConsistent
Engine switchingRequires code changesNo changes needed
Type safetyinterface{}Strongly typed
Use caseEngine-specific featuresEngine-agnostic apps

Library Usage

Basic Usage with Client SDK

package main
import (
"context""log""github.com/plexusone/omniserp""github.com/plexusone/omniserp/client"
)
funcmain() {
// Create client (auto-registers all engines and selects based on SEARCH_ENGINE env var)c, err:=client.New()
iferr!=nil {
log.Fatalf("Failed to create client: %v", err)
}
log.Printf("Using engine: %s v%s", c.GetName(), c.GetVersion())
// Perform a searchresult, err:=c.Search(context.Background(), omniserp.SearchParams{
Query: "golang programming",
NumResults: 10,
Language: "en",
Country: "us",
})
iferr!=nil {
log.Fatal(err)
}
// Use the resultlog.Printf("Search completed: %+v", result.Data)
}

Selecting a Specific Engine

// Create client with a specific enginec, err:=client.NewWithEngine("serpapi")
iferr!=nil {
log.Fatal(err)
}
// Or switch engines at runtimec.SetEngine("serper")

Capability Checking

The client SDK automatically checks if operations are supported by the current backend:

c, _:=client.New()
// Check if an operation is supportedifc.SupportsOperation(client.OpSearchLens) {
result, err:=c.SearchLens(ctx, params)
// ...
} else {
log.Println("Current engine doesn't support Lens search")
}
// Or let the client return an errorresult, err:=c.SearchLens(ctx, params)
iferrors.Is(err, client.ErrOperationNotSupported) {
log.Printf("Operation not supported: %v", err)
}

Advanced Usage with Registry

For direct registry access:

import (
"github.com/plexusone/omniserp""github.com/plexusone/omniserp/client/serper""github.com/plexusone/omniserp/client/serpapi"
)
funcmain() {
// Create registry and manually register enginesregistry:=omniserp.NewRegistry()
// Register engines (handle errors as needed)ifserperEngine, err:=serper.New(); err==nil {
registry.Register(serperEngine)
}
ifserpApiEngine, err:=serpapi.New(); err==nil {
registry.Register(serpApiEngine)
}
// Get default engine (based on SEARCH_ENGINE env var)engine, err:=omniserp.GetDefaultEngine(registry)
iferr!=nil {
log.Printf("Warning: %v", err)
}
// Perform a searchresult, err:=engine.Search(context.Background(), omniserp.SearchParams{
Query: "golang programming",
})
// ...
}

Supported Engines

Serper

  • Package: github.com/plexusone/omniserp/client/serper
  • Environment Variable: SERPER_API_KEY
  • Website: serper.dev
  • Supported Operations: All search types including Lens

SerpAPI

  • Package: github.com/plexusone/omniserp/client/serpapi
  • Environment Variable: SERPAPI_API_KEY
  • Website: serpapi.com
  • Supported Operations: All search types except Lens
  • Note: SearchLens() is not supported and will return ErrOperationNotSupported

Brave Search

  • Package: github.com/plexusone/omniserp/client/brave
  • Environment Variable: BRAVE_API_KEY
  • Website: brave.com/search/api
  • Supported Operations: Web, News, Images, Videos, Autocomplete
  • Features: Privacy-focused, fast response times, free tier available

Exa.ai

  • Package: github.com/plexusone/omniserp/client/exa
  • Environment Variable: EXA_API_KEY
  • Website: exa.ai
  • Supported Operations: Web Search, News Search, Scholar Search
  • Features: Neural search optimized for LLM applications, multiple search modes (auto, instant, fast, deep)
OperationSerperSerpAPIBraveExa
Web Search
News Search
Image Search
Video Search
Places Search
Maps Search
Reviews Search
Shopping Search
Scholar Search
Lens Search
Autocomplete
Webpage Scrape
Neural Search
Content Extract

Available Search Methods

All engines implement these methods:

typeEngineinterface {
// MetadataGetName() stringGetVersion() stringGetSupportedTools() []string// Search methodsSearch(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchNews(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchImages(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchVideos(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchPlaces(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchMaps(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchReviews(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchShopping(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchScholar(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchLens(ctx context.Context, paramsSearchParams) (*SearchResult, error)
SearchAutocomplete(ctx context.Context, paramsSearchParams) (*SearchResult, error)
// UtilityScrapeWebpage(ctx context.Context, paramsScrapeParams) (*SearchResult, error)
}

Types

SearchParams

typeSearchParamsstruct {
Querystring`json:"query"`// Required: search queryLocationstring`json:"location,omitempty"`// Optional: search locationLanguagestring`json:"language,omitempty"`// Optional: language code (e.g., "en")Countrystring`json:"country,omitempty"`// Optional: country code (e.g., "us")NumResultsint`json:"num_results,omitempty"`// Optional: number of results (1-100)
}

ScrapeParams

typeScrapeParamsstruct {
URLstring`json:"url"`// Required: URL to scrape
}

SearchResult

typeSearchResultstruct {
Datainterface{} `json:"data"`// Parsed response dataRawstring`json:"raw,omitempty"`// Raw response (optional)
}

Registry Usage

Basic Registry Operations

import (
"github.com/plexusone/omniserp""github.com/plexusone/omniserp/client/serper"
)
// Create new registry and register enginesregistry:=omniserp.NewRegistry()
// Register engines manuallyifserperEngine, err:=serper.New(); err==nil {
registry.Register(serperEngine)
}
// List available enginesengines:=registry.List()
log.Printf("Available engines: %v", engines)
// Get specific engineifengine, exists:=registry.Get("serper"); exists {
log.Printf("Using engine: %s v%s", engine.GetName(), engine.GetVersion())
}
// Get all enginesallEngines:=registry.GetAll()

Engine Information

// Get info about specific engineengine, _:=registry.Get("serper")
info:=omniserp.GetEngineInfo(engine)
log.Printf("Engine: %s v%s, Tools: %v", info.Name, info.Version, info.SupportedTools)
// Get info about all enginesallInfo:=omniserp.GetAllEngineInfo(registry)

Environment Configuration

The package uses environment variables for configuration:

# Choose which engine to use (optional, defaults to "serper")export SEARCH_ENGINE="serper"# or "serpapi", "brave", "exa"# API keys for respective enginesexport SERPER_API_KEY="your_serper_key"export SERPAPI_API_KEY="your_serpapi_key"export BRAVE_API_KEY="your_brave_key"export EXA_API_KEY="your_exa_key"

Adding New Engines

To add a new search engine:

  1. Create engine package under client/:
// client/newengine/newengine.gopackage newengine
import (
"context""fmt""os""github.com/plexusone/omniserp"
)
typeEnginestruct {
apiKeystring// other fields
}
funcNew() (*Engine, error) {
apiKey:=os.Getenv("NEWENGINE_API_KEY")
ifapiKey=="" {
returnnil, fmt.Errorf("NEWENGINE_API_KEY required")
}
return&Engine{apiKey: apiKey}, nil
}
func (e*Engine) GetName() string { return"newengine" }
func (e*Engine) GetVersion() string { return"1.0.0" }
func (e*Engine) GetSupportedTools() []string { /* return supported tools */ }
// Implement all other omniserp.Engine methods...func (e*Engine) Search(ctx context.Context, params omniserp.SearchParams) (*omniserp.SearchResult, error) {
// Implementation
}
// ... implement all other interface methods
  1. Register in your application:
// In your application code (e.g., cmd/yourapp/main.go)import (
"github.com/plexusone/omniserp""github.com/plexusone/omniserp/client/newengine""github.com/plexusone/omniserp/client/serper"
)
funccreateRegistry() *omniserp.Registry {
registry:=omniserp.NewRegistry()
// Register existing enginesifserperEngine, err:=serper.New(); err==nil {
registry.Register(serperEngine)
}
// Register new engineifnewEng, err:=newengine.New(); err==nil {
registry.Register(newEng)
}
returnregistry
}
  1. Update CLI (optional): Add the new engine import and registration to cmd/omniserp/main.go

Error Handling

The package provides consistent error handling:

engine, err:=omniserp.GetDefaultEngine(registry)
iferr!=nil {
// Handle engine selection errorlog.Printf("Engine selection warning: %v", err)
}
result, err:=engine.Search(ctx, params)
iferr!=nil {
// Handle search errorlog.Printf("Search failed: %v", err)
}

Examples

See the examples/ directory for working examples:

  • capability_check/: Demonstrates capability checking, engine switching, and operation support matrix
  • normalized_search/: Shows normalized responses and engine-agnostic code

To run an example:

export SERPER_API_KEY="your_key"export SERPAPI_API_KEY="your_key"# optional# Check capabilities
go run examples/capability_check/main.go
# Demonstrate normalized responses
go run examples/normalized_search/main.go "golang programming"

Testing

Run tests without API keys (tests will skip gracefully):

go test ./...

Run tests with API calls (requires API keys):

export SERPER_API_KEY="your_key"export SERPAPI_API_KEY="your_key"
go test -v ./client

Thread Safety

The registry is safe for concurrent read operations. Engine implementations should be thread-safe for concurrent use.

About

Multi-provider abstraction with MCP server for Serp search engine providers including Serper.Dev and SERP API.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages