🔴 IMPORTANT: The production deployment at https://audience-agent.fly.dev is an MCP server for AI clients, not a web application.
The production server exposes Model Context Protocol (MCP) tools via HTTP JSON-RPC:
Base URL: https://audience-agent.fly.dev
Endpoints:
POST /mcp- Main MCP JSON-RPC endpointGET /tools- List available toolsGET /health- Health checkPOST /tools/{tool_name}- REST-style tool access
{
"servers": {
"audience-agent": {
"url": "https://audience-agent.fly.dev/mcp",
"transport": "http",
"description": "Audience discovery and activation agent"
}
}
}-
get_signal_examples
- Returns examples of how to use the signal discovery tasks
- No parameters required
-
get_signals
- Discover audience segments based on natural language
- Parameters:
signal_spec(string, required): Natural language descriptiondeliver_to(object, required): Delivery specificationlimit(integer, optional): Max results (default: 20)
-
activate_signal
- Activate a segment on a decisioning platform
- Parameters:
signals_agent_segment_id(string, required)platform(string, required)context_id(string, required)principal_id(string, optional)
curl -X POST https://audience-agent.fly.dev/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "get_signals",
"params": {
"signal_spec": "luxury car buyers",
"deliver_to": {
"platforms": ["index-exchange"],
"max_cpm": 5.0
},
"limit": 10
},
"id": 1
}'# Install dependencies
uv pip install fastmcp rich google-generativeai requests- Copy the sample config:
cp config.json.sample config.json- Edit
config.jsonto add:- Your Gemini API key
- Platform credentials (e.g., Index Exchange username/password)
- Principal-to-account mappings
uv run python main.pyuv run python client.py# Basic search
uv run python client.py --prompt "luxury car buyers"
# With principal ID (for account-specific segments)
uv run python client.py --prompt "luxury" --principal acme_corp
# Limit results
uv run python client.py --prompt "automotive" --limit 10The LiveRamp adapter provides access to the full LiveRamp Data Marketplace catalog with over 200,000 segments. It uses an offline sync approach for optimal performance.
- Full Catalog Sync: Downloads entire LiveRamp catalog to local SQLite database
- Offline Search: Uses SQLite FTS5 for fast, intelligent full-text search
- Scheduled Updates: Fly.io scheduled machines for daily catalog updates
- Batch Processing: Memory-efficient processing of large datasets
- Secure Credentials: Environment variable-based credential management
"liveramp": {
"enabled": true,
"base_url": "https://api.liveramp.com",
"client_id": "your-client-id",
"account_id": "your-service-account",
"secret_key": "your-secret-key",
"token_uri": "your-token-uri",
"owner_org": "your-owner-org"
}- Run manual sync:
uv run python sync_liveramp_catalog.py --full - Scheduled sync: Configured in Fly.io to run daily
- Database location:
/data/signals_agent.db(Fly.io) orsignals_agent.db(local)
- The adapter ALWAYS uses the local cache - no automatic API calls
- Sync is handled separately by the scheduled job
- Full catalog is available without pagination limits
- FTS5 queries are sanitized to prevent injection
Platform adapters wrap decisioning platform APIs to provide unified access to audience segments.
-
Base Adapter (
adapters/base.py)- Abstract base class defining the adapter interface
- Built-in caching with configurable TTL (default 60 seconds)
- Principal validation for security
-
Index Exchange Adapter (
adapters/index_exchange.py)- Full authentication with token refresh
- Segment normalization to internal format
- Transparent data availability:
- Returns
Nonefor coverage when not available - Returns
Nonefor CPM when no fees configured - Sets
has_coverage_dataandhas_pricing_dataflags
- Returns
-
Adapter Manager (
adapters/manager.py)- Manages multiple platform adapters
- Automatically determines adapter class from platform name
- Maps principals to platform accounts
The system explicitly indicates when data is not available:
- Coverage displays as "Unknown" when no data exists
- CPM displays as "Unknown" when no pricing data exists
- No smart estimation or guessing of values
-
Create a new adapter class inheriting from
PlatformAdapter -
Implement required methods:
authenticate()- Handle platform authenticationget_segments()- Fetch and normalize segmentsactivate_segment()- Activate a segment on the platformcheck_segment_status()- Check activation status
-
Update the adapter manager to recognize the new platform:
def _get_adapter_info(self, platform_name: str, platform_config: Dict[str, Any]) -> tuple[str, str]:
if platform_name == 'your-platform':
return 'YourPlatformAdapter', 'adapters.your_platform'"platforms": {
"index-exchange": {
"enabled": true,
"test_mode": false,
"base_url": "https://app.indexexchange.com/api",
"username": "your-username",
"password": "your-password",
"cache_duration_seconds": 60,
"principal_accounts": {
"principal_id": "account_id"
}
}
}- Maps principal IDs to platform account IDs
- Enables multi-tenant access control
- Principals only see segments from their mapped accounts
- Configure real IX credentials in
config.json - Map principals to account IDs
- Run searches with principal ID to see account-specific segments
# Search public segments
uv run python client.py --prompt "automotive enthusiasts"
# Search with principal (includes platform segments)
uv run python client.py --prompt "luxury" --principal acme_corp
# Interactive discovery
uv run python client.py
> discover
> luxury travel
> 1 # Choose specific platforms
> index-exchange- This is by design - the system shows "Unknown" when data is not available
- Index Exchange segments without fees show "Unknown" CPM
- Segments without coverage data show "Unknown" coverage
- Check platform credentials in config.json
- Ensure the account has API access enabled
- Verify principal-to-account mappings
- Check if platform is enabled in config
- Verify principal has mapped account
- Check platform API is accessible
To prevent "Expression tree too large" errors when processing large datasets, the system uses configurable limits:
# Maximum segments to send to AI for ranking (default: 20)
export MAX_SEGMENTS_FOR_AI=15
# Maximum segments to include in AI ranking prompts (default: 20)
export MAX_SEGMENTS_FOR_PROMPT=15Lower values reduce memory usage and prevent expression tree depth errors but may reduce result quality for very large catalogs.
- Transparent Data: Never estimate or guess values - show "Unknown"
- Caching: 60-second cache to reduce API load
- Security: Principal-based access control for multi-tenancy
- Extensibility: Easy to add new platform adapters
- Memory Safety: Conservative limits on AI processing to prevent expression tree depth errors
- Proper Search Architecture: Database segments now use FTS5/RAG/hybrid search instead of primitive SQL LIKE
- No Expression Tree Depth Issues: Eliminated deeply nested SQL OR queries that caused SQLite EXPR_DEPTH errors
- Intelligent Search Strategy: RAG for semantic queries, FTS for exact matching, hybrid for balanced results
- Graceful Fallbacks: FTS search when RAG is unavailable, AI ranking fallback to text-based ranking
- Optimized Prompts: JSON payloads to Gemini are truncated and simplified to prevent parsing errors
- Generate vector embeddings for signal_segments table to enable full RAG search
- Implement hybrid search combining FTS5 + vector similarity for database segments
- Add more platform adapters (Trade Desk, DV360, etc.)
- Implement segment activation and status checking
- Add webhook support for activation notifications
- Support for custom segment creation on platforms