MCP Insights Proxy
# MCP Insights Proxy v2
**Context-efficient MCP server** that executes OpenSearch queries and returns **synthesized output** instead of raw JSON.
## Philosophy
> **Full query flexibility, synthesized output only.**
You retain 100% of OpenSearch DSL capabilities. The proxy only transforms the *output*.
```
┌──────────────────────────────────────────────────────────────────────────┐
│ BEFORE (direct OpenSearch MCP) │
│ Query → OpenSearch → 500+ lines raw JSON → Context window 💥 │
├──────────────────────────────────────────────────────────────────────────┤
│ AFTER (this proxy) │
│ Query → Proxy → OpenSearch → Proxy formats → 20-50 lines → Context ✅ │
└──────────────────────────────────────────────────────────────────────────┘
```
## Key Difference from v1
| v1 (Limited) | v2 (Full Flexibility) |
|--------------|----------------------|
| 5-6 predefined tools | 1 main tool accepting ANY DSL |
| Hardcoded query patterns | You build the query |
| Limited aggregations | ALL aggregations supported |
| No nested/has_parent | Full query DSL support |
## Quick Start
### 1. Install
```bash
cd mcp-insights-proxy
npm install
npm run build
```
### 2. Configure MCP Client
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
{
"mcpServers": {
"insights-proxy": {
"command": "node",
"args": ["/absolute/path/to/mcp-insights-proxy/dist/index.js"],
"env": {
"OPENSEARCH_URL": "https://your-cluster:9200",
"OPENSEARCH_INDEX": "channel_posts",
"OPENSEARCH_USER": "username",
"OPENSEARCH_PASS": "password"
}
}
}
}
```
### 3. Restart Claude
## Tools
### `opensearch_query` (Main Tool)
Execute **any** OpenSearch DSL query. Full flexibility.
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `query` | object | **Required.** Full OpenSearch DSL query object |
| `index` | string | Index name (default: `channel_posts`) |
| `output_format` | enum | `auto`, `table`, `list`, `summary`, `compact_json` |
| `max_display` | number | Max hits to show (default: 20, max: 50) |
**Example - Complex aggregation with nested sub-aggs:**
```json
{
"query": {
"size": 0,
"query": {
"bool": {
"must": [
{"term": {"join_field": "post"}},
{"term": {"channel.type": "ig"}}
],
"filter": [
{"range": {"published_at": {"gte": "now-30d"}}}
]
}
},
"aggs": {
"by_hashtag": {
"terms": {"field": "hashtags", "size": 10},
"aggs": {
"avg_engagement": {"avg": {"field": "engagement"}},
"top_creators": {"terms": {"field": "channel.name", "size": 3}}
}
}
}
}
}
```
**Example - has_parent query:**
```json
{
"query": {
"query": {
"bool": {
"must": [
{"term": {"join_field": "post"}},
{
"has_parent": {
"parent_type": "channel",
"query": {
"bool": {
"must": [
{"term": {"channel.geo.country.code": "IT"}},
{"range": {"channel.followers": {"gte": 100000}}}
]
}
}
}
}
]
}
},
"sort": [{"engagement": "desc"}],
"size": 10,
"_source": ["channel.name", "engagement", "published_at"]
}
}
```
### `opensearch_count`
Quick count without full query overhead.
```json
{
"query": {
"bool": {
"must": [
{"term": {"join_field": "post"}},
{"term": {"hashtags": "skincare"}}
]
}
}
}
```
### `opensearch_mapping`
Get field list for an index.
## Output Formats
### `auto` (default)
Automatically detects:
- Aggregation-only → summary format
- Few hits (≤5) → list format
- Many hits → table format
### `table`
```
| # | name | type | engagement | likes | published_at |
|---|------|------|------------|-------|--------------|
| 1 | creator1 | IG | 156K | 142K | 2025-01-15 |
| 2 | creator2 | TT | 98K | 89K | 2025-01-18 |
```
### `list`
```
**1.** @creator1 (IG) • eng: 156K • likes: 142K • views: 2.3M • 2025-01-15
**2.** @creator2 (TT) • eng: 98K • likes: 89K • views: 1.8M • 2025-01-18
```
### `summary`
Just counts and aggregation results, no individual hits.
### `compact_json`
Minimal JSON with only `_source` (no `_id`, `_score`, `_index` metadata).
## Aggregation Output Examples
### Terms aggregation
```
**by_platform:**
• ig: 45.2K
• tt: 32.1K
• yt: 12.8K
```
### Stats aggregation
```
**engagement_stats:** count=89.1K avg=2.3K min=0 max=1.2M sum=205M
```
### Nested sub-aggregations
```
**by_hashtag:**
• **skincare** (12.4K)
**avg_engagement:** 3.2K
**top_creators:**
• creator1: 342
• creator2: 287
• **beauty** (8.7K)
**avg_engagement:** 2.8K
...
```
## Context Savings
| Query Type | Raw JSON | Proxy Output | Savings |
|------------|----------|--------------|---------|
| Top 10 posts | ~3000 tokens | ~300 tokens | **90%** |
| Aggregation (5 buckets) | ~1500 tokens | ~150 tokens | **90%** |
| Complex nested agg | ~5000 tokens | ~400 tokens | **92%** |
## Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `OPENSEARCH_URL` | Yes | `http://localhost:9200` | Cluster URL |
| `OPENSEARCH_INDEX` | No | `channel_posts` | Default index |
| `OPENSEARCH_USER` | No | - | Basic auth username |
| `OPENSEARCH_PASS` | No | - | Basic auth password |
## Development
```bash
npm run dev # Development with auto-reload
npm run build # Build for production
npm start # Run production build
```
## Architecture
```
┌─────────────┐ ┌────────────────────────────────────────────┐ ┌────────────┐
│ │ │ MCP Insights Proxy │ │ │
│ Claude │────▶│ 1. Receive DSL query (any complexity) │────▶│ OpenSearch │
│ (builds │ │ 2. Execute against cluster │ │ │
│ full │◀────│ 3. Parse response │◀────│ │
│ DSL) │ │ 4. Format: table/list/summary │ │ │
│ │ │ 5. Return compact markdown │ │ │
└─────────────┘ └────────────────────────────────────────────┘ └────────────┘
│ │
│ Returns:
│ "*45.2K hits • took 23ms*
│
│ ## Aggregations
│ **by_platform:**
│ • ig: 45.2K
│ • tt: 32.1K
│
│ ## Results
│ | # | name | engagement |..."
│
└─── ~300 tokens instead of ~3000
```
## License
MIT
TDQS
Scored across 3 tools
Each tool has a clearly distinct purpose: count for lightweight counts, query for arbitrary DSL queries, and mapping for schema inspection. No overlap or ambiguity between them.
All tool names follow the same pattern: 'opensearch_' prefix followed by a single descriptive verb/noun (count, query, mapping). This is perfectly consistent and predictable.
With 3 tools, the set is compact and focused on core OpenSearch operations (count, query, mapping). It feels slightly minimal but appropriate for a read-only insights proxy, and each tool earns its place.
The domain is OpenSearch insights/querying, and the set covers count, query, and schema discovery, which are the essential operations. Minor gaps like direct document fetch or index listing can be handled via queries or mapping, so no critical dead ends.