# MCP-RLM Proxy Examples

This directory contains usage examples for the MCP-RLM Proxy Server.

## Available Examples

### 1. `comprehensive_example.py` ⭐ (Recommended)

A complete demonstration of all proxy features including:
- Multi-server tool aggregation
- `proxy_filter` with Python REPL (code-based transformations)
- `proxy_search` with multiple modes (regex, bm25, fuzzy, context)
- Combined `proxy_filter` + `proxy_search`
- Recursive exploration with `proxy_explore` (RLM pattern)
- Token savings calculations

**Run it:**
```bash
cd examples
python comprehensive_example.py
```

This is the best starting point to understand all capabilities.

### 2. `example_usage.py`

Interactive examples showing:
- `proxy_filter` syntax with Python REPL
- `proxy_search` patterns and modes
- Combined RLM workflow patterns

**Run it:**
```bash
cd examples
python example_usage.py
```

**Note**: Requires configured servers in `mcp.json` (see `mcp.json.example`)

## Quick Examples

### Transformation/Filtering (via `proxy_filter`)

Execute Python code to programmatically transform data. The cached data is available as variable `data` in a sandboxed Python environment.

```python
# After a large response is truncated, you'll receive a cache_id like "agent_1:ABC123DEF456".
# Use proxy_filter with Python code to transform the data.

# Simple field projection
result = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:ABC123DEF456",
    "code": "[{k: item[k] for k in ['name', 'email']} for item in data]",
    "return_format": "json"
})

# Filtering with conditions
result = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:ABC123DEF456",
    "code": "[item for item in data if item.get('status') == 'active']"
})

# Aggregation
result = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:ABC123DEF456",
    "code": "{'total': len(data), 'avg_score': sum(item.get('score', 0) for item in data) / len(data) if data else 0}"
})

# Exclude sensitive fields
result = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:ABC123DEF456",
    "code": "{k: v for k, v in data.items() if k not in {'password', 'ssn', 'api_key'}}"
})

# Fresh call mode (call tool and filter in one step)
result = await session.call_tool("proxy_filter", {
    "tool": "filesystem_read_file",
    "arguments": {"path": "data.json"},
    "code": "[item['name'] for item in data]"
})
```

### Search (via `proxy_search`)

Search within cached or fresh tool results. Supports multiple modes: `regex`, `bm25`, `fuzzy`, `context`.

```python
# Regex search with context
result = await session.call_tool("proxy_search", {
    "cache_id": "agent_1:ABC123DEF456",
    "pattern": "ERROR|FATAL",
    "mode": "regex",
    "case_insensitive": True,
    "context_lines": 2,
    "max_results": 50
})

# BM25 relevance search
result = await session.call_tool("proxy_search", {
    "cache_id": "agent_1:ABC123DEF456",
    "pattern": "database connection timeout",
    "mode": "bm25",
    "top_k": 5
})

# Fuzzy search (handles typos)
result = await session.call_tool("proxy_search", {
    "cache_id": "agent_1:ABC123DEF456",
    "pattern": "authentication",
    "mode": "fuzzy",
    "threshold": 0.7
})

# Fresh call mode (call tool and search in one step)
result = await session.call_tool("proxy_search", {
    "tool": "filesystem_read_file",
    "arguments": {"path": "logs/app.log"},
    "pattern": "ERROR",
    "mode": "regex"
})
```

### Combined Transformations (proxy_filter + proxy_search)

```python
# Step 1: Call underlying tool through the proxy (may be truncated + cached)
users = await session.call_tool("api_get_users", {"limit": 1000})
# Assume response was truncated and you received cache_id="agent_1:ABC123DEF456"

# Step 2: Transform/filter the cached result using Python REPL
projected = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:ABC123DEF456",
    "code": "[{k: item[k] for k in ['name', 'email', 'status']} for item in data]"
})

# Step 3: Search within the (same) cached result
search_results = await session.call_tool("proxy_search", {
    "cache_id": "agent_1:ABC123DEF456",
    "pattern": "gmail\\.com",
    "mode": "regex"
})
```

### Recursive Exploration (via `proxy_explore`)

Discover data structure without loading all content:

```python
# Step 1: Call underlying tool (may be truncated + cached)
data = await session.call_tool("api_get_data", {})
# Assume cache_id="agent_1:ABC123DEF456"

# Step 2: Explore structure
structure = await session.call_tool("proxy_explore", {
    "cache_id": "agent_1:ABC123DEF456",
    "max_depth": 3
})
# Returns: types, field names, sizes, sample

# Step 3: Use proxy_filter based on discovered structure
filtered = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:ABC123DEF456",
    "code": "[{k: item[k] for k in ['id', 'name']} for item in data]"
})
```

## Using with Different Clients

### Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "proxy": {
      "command": "mcp-rlm-proxy",
      "args": ["--config", "/absolute/path/to/mcp.json"]
    }
  }
}
```

Then ask Claude to use the proxy tools `proxy_filter`, `proxy_search`, and `proxy_explore`
with the `cache_id` values returned from large tool responses.

### Custom Python Client

See `comprehensive_example.py` for complete client implementation.

### Node.js/TypeScript

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "mcp-rlm-proxy",
  args: ["--config", "/absolute/path/to/mcp.json"]
});

const client = new Client({
  name: "example-client",
  version: "1.0.0"
}, {
  capabilities: {}
});

await client.connect(transport);

// Use proxy tools
const result = await client.callTool({
  name: "proxy_filter",
  arguments: {
    cache_id: "agent_1:ABC123DEF456",
    code: "[{k: item[k] for k in ['name', 'email']} for item in data]",
    return_format: "json"
  }
});
```

## Real-World Use Cases

### Use Case 1: Log Analysis Agent

```python
# Agent needs to find authentication failures in logs
# 1) Call filesystem_read_file via proxy; assume it was truncated with cache_id="agent_1:LOGS123456".
# 2) Use proxy_search on the cached log content.
failures = await session.call_tool("proxy_search", {
    "cache_id": "agent_1:LOGS123456",
    "pattern": "authentication failure|Failed password",
    "mode": "regex",
    "case_insensitive": True,
    "context_lines": 2,
    "max_results": 100
})

# Result: Only relevant log lines (99.7% token savings)
```

### Use Case 2: API Integration

```python
# Agent needs user emails from large API response
# 1) Call api_list_users via proxy; assume response cached with cache_id="agent_1:USERS123456".
# 2) Use proxy_filter with Python REPL to get only IDs and emails.
users = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:USERS123456",
    "code": "[{'id': item['id'], 'email': item['email']} for item in data.get('users', [])]",
    "return_format": "json"
})

# Result: Only IDs and emails (95% token savings)
```

### Use Case 3: Security/Privacy

```python
# Agent needs user data but must exclude sensitive fields
# 1) Call db_get_user via proxy; assume response cached with cache_id="agent_1:USER123456".
# 2) Use proxy_filter with Python REPL to exclude sensitive fields.
user = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:USER123456",
    "code": "{k: v for k, v in data.items() if k not in {'password', 'ssn', 'credit_card', 'api_key'}}"
})

# Result: All data except sensitive fields
```

### Use Case 4: Recursive Exploration (RLM)

```python
# Step 1: Call api_get_data via proxy; assume response cached with cache_id="agent_1:DATA123456".
# Step 2: Use proxy_explore to discover structure.
structure = await session.call_tool("proxy_explore", {
    "cache_id": "agent_1:DATA123456",
    "max_depth": 3
})
# Returns: types, field names, sizes, sample

# Step 3: Use proxy_filter with Python REPL to get an overview of key fields.
overview = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:DATA123456",
    "code": "[{k: item[k] for k in ['id', 'name']} for item in data]"
})

# Step 4: Drill down into a specific nested field using Python REPL.
details = await session.call_tool("proxy_filter", {
    "cache_id": "agent_1:DATA123456",
    "code": "[item.get('profile', {}).get('bio') for item in data if item.get('profile')]"
})

# Total: ~98% token savings vs loading everything
```

## Tips

1. **Start Simple**: Begin with basic `proxy_filter` using simple Python code, then add `proxy_search` as needed
2. **Monitor Savings**: Check proxy logs for token reduction metrics
3. **Design Flows**: Design reusable `proxy_filter`/`proxy_search` flows for common queries
4. **Iterate**: Use RLM pattern (`proxy_explore` → `proxy_filter` → `proxy_search`) to explore, then query
5. **Test Patterns**: Use `comprehensive_example.py` to test different proxy tool flows
6. **Python REPL**: `proxy_filter` uses Python REPL - you can use any Python expression that returns a value
7. **Fresh Calls**: All proxy tools support `tool` + `arguments` for fresh calls (no cache_id needed)

## Troubleshooting

### "Server not found"

Ensure the proxy is configured with servers in `mcp.json`:

```json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    }
  }
}
```

### "Tool name not found"

Tools are prefixed with server names:
- `read_file` → `filesystem_read_file`
- `commit` → `git_commit`

List available tools:

```python
tools = await session.list_tools()
print([t.name for t in tools.tools])
```

### "proxy_filter not working"

Make sure you're using the correct syntax:

1. **REPL mode** (recommended): Provide `code` parameter with Python code
   ```python
   {"cache_id": "...", "code": "[item for item in data if item['active']]"}
   ```

2. **Declarative mode** (backward compatibility): Provide `fields` or `exclude` parameter
   ```python
   {"cache_id": "...", "fields": ["name", "email"], "mode": "include"}
   ```

3. The cached data is available as variable `data` in your Python code
4. Code should return the transformed result (not print it)

## More Resources

- [Quick Reference](../docs/QUICK_REFERENCE.md) - Complete syntax reference
- [Middleware Adoption](../docs/MIDDLEWARE_ADOPTION.md) - Integration guide
- [Migration Guide](../docs/MIGRATION_GUIDE.md) - Migrating from direct MCP
- [Architecture](../docs/ARCHITECTURE.md) - How it works internally

## Contributing Examples

Have a great use case? Contribute an example:

1. Create a new `.py` file in `examples/`
2. Add documentation at the top
3. Follow the pattern in `comprehensive_example.py`
4. Submit a PR

Examples especially welcome for:
- Industry-specific use cases
- Novel projection/grep patterns
- Integration with specific AI frameworks
- Performance optimization techniques
