# Taming Chatty APIs: Implementing Smart Field Selection in MCP Servers

Model Context Protocol (MCP) servers that proxy to REST APIs face a common challenge: API responses can be incredibly verbose, leading to token exhaustion, context window bloat, and poor signal-to-noise ratios. When an AI agent just needs a post title and author name, why should it receive 50 fields of metadata, nested objects, and arrays?

## The Problem: API Verbosity Kills Performance

REST APIs are designed for flexibility, often returning comprehensive data structures. Consider a typical blog post response from an API:

```json
{
  "id": "123",
  "title": "My Blog Post",
  "content": "Lorem ipsum...",
  "author": {
    "id": "456",
    "name": "John Doe",
    "email": "john@example.com",
    "profilePicture": "https://...",
    "bio": "A long biography...",
    "socialLinks": {...},
    "preferences": {...}
  },
  "organization": {
    "id": "789",
    "name": "Acme Corp",
    "domain": "acme.com",
    "settings": {...},
    "billing": {...}
  },
  "metadata": {
    "views": 1234,
    "likes": 56,
    "comments": 78,
    "lastModified": "2024-01-01T00:00:00Z",
    "seoData": {...},
    "analytics": {...}
  },
  // ... dozens more fields
}
```

When an AI agent asks "What are the recent blog posts?", it doesn't need billing information, SEO data, or full user profiles. This verbose response:

1. **Wastes tokens** - Each unnecessary field costs money
2. **Fills context windows** - Less room for actual conversation
3. **Reduces accuracy** - Important information gets lost in noise
4. **Slows processing** - More data to parse and understand

## The Solution: Auto-Discovery Field Selection

The ideal solution lets agents dynamically specify which fields they want, similar to GraphQL's field selection but simpler. Here's what we implemented:

### 1. Rich Tool Descriptions with Field Discovery

Instead of generic descriptions, we embedded field schemas directly in tool descriptions:

```typescript
{
  name: 'list_posts',
  description: 'List posts with optional filtering. Available fields: id, title, content, author(name,email), organization(name), upvotes, postCategory(category), postTags(name), postStatus(name), date, lastModified'
}
```

This gives agents immediate visibility into:

- Available top-level fields (`id`, `title`, `content`)
- Nested object fields (`author(name,email)`)
- Array structures (`postTags(name)`)

### 2. JSON Mask Selection Syntax

We chose the proven `json-mask` library syntax that's intuitive for AI agents:

```javascript
// Simple field selection
"id,title,upvotes"

// Nested object selection
"title,author(name,email)"

// Array element selection  
"postTags(name),author(name)"

// Complex combinations
"id,title,author(name,email),postCategory(category),postTags(name)"
```

### 3. Optional Parameter Design

The `select` parameter is completely optional:

```typescript
select: {
  type: 'string',
  description: 'Fields to return. Examples: "id,title,upvotes" | "title,author(name)" | "postCategory(category),postStatus(name)". Leave empty for all fields.',
}
```

No selection = full unfiltered response. This ensures backward compatibility and lets agents choose their level of optimization.

## Implementation Details

### Using the JSON Mask Library

Rather than reinventing the wheel, we leveraged the battle-tested `json-mask` library:

```bash
npm install json-mask @types/json-mask
```

```typescript
import mask from 'json-mask';
```

The beauty of `json-mask` is its simplicity - it handles all the complex parsing and field selection logic for us.

### Integration with Request Handlers

The integration is remarkably simple:

```typescript
case 'list_posts': {
  const { select, ...apiArgs } = args as any;
  const result = await this.api.listPosts(apiArgs);
  
  const filtered = select 
    ? { ...result, results: result.results?.map((post: any) => mask(post, select)) || [] }
    : result;
    
  return {
    content: [{
      type: 'text',
      text: JSON.stringify(filtered, null, 2),
    }],
  };
}
```

Just `mask(data, selectString)` - that's it! No complex parsing, no recursive functions, no edge case handling.

## Real-World Impact

### Before Field Selection

```bash
Agent: "Show me recent posts"
Response: 15KB of JSON with full user profiles, organization details, metadata...
Tokens: ~4,000
Context: Heavily polluted
```

### After Field Selection  

```bash
Agent: "Show me recent posts" (with select: "id,title,author(name)")
Response: 2KB of clean, focused data
Tokens: ~500
Context: Clean and relevant
```

**Result: 87% reduction in response size and token usage.**

## Why This Approach Works

### 1. **Autodiscovery Through Documentation**

Agents learn available fields from tool descriptions without needing separate schema introspection calls.

### 2. **Proven Syntax**

The `json-mask` syntax is battle-tested and feels natural to AI agents that understand nested data structures.

### 3. **Progressive Enhancement**

Works perfectly without field selection, adds optimization when needed.

### 4. **Zero Breaking Changes**

Existing integrations continue working unchanged.

### 5. **Simple Implementation**

Using `json-mask` makes it trivial to add to any MCP server - just one function call.

## Best Practices for Implementation

### 1. Keep Field Descriptions Current

Update tool descriptions when API schemas change:

```typescript
// ❌ Outdated
description: 'Get users. Fields: id, name, email'

// ✅ Current and comprehensive  
description: 'Get users. Available fields: id, name, email, profile(avatar,bio), preferences(theme,notifications), company(name,role)'
```

### 2. Provide Realistic Examples

Show common use cases in parameter descriptions:

```typescript
select: {
  description: 'Fields to return. Examples: "name,email" | "profile(avatar)" | "company(name,role)". Leave empty for all fields.'
}
```

### 3. Let JSON Mask Handle the Details

```typescript
// json-mask handles missing fields, nested structures, and arrays automatically
const filtered = mask(data, selectString);
```

### 4. Consider Performance

For large datasets, field selection can dramatically improve performance:

```typescript
// Before: 1000 posts × 50 fields = 50,000 data points
// After: 1000 posts × 3 fields = 3,000 data points
```

## Future Enhancements

### 1. **Field Aliasing**

```javascript
"author(name:authorName,email:authorEmail)"
```

### 2. **Conditional Fields**

```javascript
"author(name,email?admin)"  // email only if admin
```

### 3. **Advanced Filtering**

```javascript
"posts(title,author(name),createdAt|date>2024-01-01)"
```

### 4. **Field Validation**

Validate requested fields against schema and provide helpful error messages.

## Conclusion

Field selection transforms chatty APIs into efficient, focused data sources. By leveraging the proven `json-mask` library with auto-discovery through rich documentation, we've created a system that:

- **Reduces token costs by 80-90%**
- **Improves response relevance**
- **Maintains backward compatibility**
- **Takes just minutes to implement**

The next time you're building an MCP server that proxies to a verbose API, just `npm install json-mask` and add a `select` parameter. Your AI agents (and your token bill) will thank you.

---

*Want to see this in action? Check out the [Featurebase MCP Server](https://github.com/marcinwyszynski/featurebase-mcp) implementation that demonstrates these patterns.*
