Building MCP Servers from Protobuf (Part4) - Insights from Running MCP Tools in Practice

October 11, 2025

Introduction

In this blog series, we’ll show you how to build an MCP (Model Context Protocol) server packed with useful tools. Rather than starting from scratch, we’ll take advantage of our existing Protocol Buffers and Google’s gRPC transcoding. By creating a custom protoc (Protocol Buffer compiler) plugin, we can automatically generate the MCP server. This unified approach lets us produce gRPC services, OpenAPI specifications, REST APIs, and the MCP server all from the same source.

This blog series contains 4 articles:

What You'll Get

In the process to test our auto-generated MCP tools, we discovered critical limitations. This article shares practical insights from running MCP tools with real AI Agent, covering:

  • Critical limitations of auto-generated MCP tools
  • Why tool quantity matters (keep under 20)
  • How response size breaks AI workflows
  • When auto-generation works vs. when custom design is needed

Key Challenges with auto-generated MCP tools

Challenge 1: Too Many Tools Cause Confusion

Converting all API endpoints to MCP tools creates overwhelming choice. One service generated 20+ tools, causing:

  • Incorrect tool selection: AI choosing similar-sounding but wrong tools
  • Retry loops: Repeatedly trying wrong tool variations
  • Poor tool chaining: Failed multi-step workflows
  • Degraded UX: Excessive clarification questions

The solution is to keep tool count under 20 per AI agent. Create focused tool sets:

# Instead of 20 tools, create focused groups:
bookstore_core = ["get_book", "create_book", "search_books"]  # 3 tools
user_management = ["get_user", "create_user", "authenticate_user"]  # 3 tools
order_processing = ["create_order", "get_order_status", "process_payment"]  # 3 tools
...

Challenge 2: ID-Based vs. Name-Based Inputs

Traditional APIs use IDs for programmatic access:

// Traditional (developer-friendly)
message GetBookRequest {
 string book_id = 1;  // "550e8400-e29b-41d4-a716-446655440000"
}

AI agents work with natural language. Users say "get book 'Learning Python'" not "get book ID 550e8400...".

The solution is to accept natural language as inputs, for example:

// AI-friendly (user-friendly)
message SearchBookRequest {
 string title = 1;
 string author = 2;  // Optional filter
}

Challenge 3: Complex Nested Structures For Input Params

Traditional APIs use nested structures for flexibility. AI agents struggle with these because:

  • Difficulty understanding field relationships
  • Mistakes constructing complex objects

Avoid:

message CreateOrderRequest {
 OrderDetails order = 1;
 PaymentInfo payment = 2;
 repeated PromotionCode promotions = 4;
 OrderOptions options = 5;
}

Better:

message CreateSimpleOrder {
 string customer_name = 1;
 string item_name = 2;
 int32 quantity = 3;
 string shipping_address = 4;
}

Challenge 4: Large Response Payloads

Traditional APIs return comprehensive nested data. Auto-generated tools can't customize responses, causing context window overflow.

For example, a get_location tool returning complete infrastructure, compliance, and network data could generate large JSON, consuming most of the AI's context window.

The solution is to not expose comprehensive APIs as MCP tools. Use them internally and expose task-focused tools:

# NOT an MCP tool - internal helper
async def _get_location_internal(location_id: str) -> Location:
   response = await location_service.GetLocation(location_id)
   return response.location

# MCP tool - focused task
async def validate_deployment_location(location_name: str) -> ValidationResult:
   """Check if location meets deployment requirements."""
   location = await _get_location_internal(location_name)
   
   # Process internally
   checks = _validate_compliance_capacity_network(location)
   
   # Return only essentials
   return ValidationResult(
       location_name=location.name,
       is_valid=checks.passed,
       summary=f"Location {location.name} is {'ready' if checks.passed else 'not ready'}"
   )

When Auto-Generation Works vs. Custom Design

Auto-generation is excellent for:

  • Rapid prototyping and proof-of-concept
  • Internal tools for team members
  • Simple CRUD operations
  • Development/testing

Custom MCP tools needed for:

  • Production deployment
  • APIs with 20+ endpoints
  • ID-based systems
  • Complex nested structures
  • Large response payloads
  • User-facing applications

LLM Model Selection Impact

The choice of LLM model significantly affects tool design strategy:

Powerful LLM Models 

Powerful models can work with simpler, concise descriptions because they:

  • Excel at inferring context and intent from minimal information
  • Understand implicit relationships between tools
  • Handle ambiguity and edge cases gracefully
async def create_database_cluster():
   """Create a new database cluster. Requires: name, region, instance_type."""

These models can chain tools intelligently with minimal guidance.

Less Powerful Models

Smaller or less capable models need detailed, explicit descriptions to compensate for limited reasoning:

async def create_database_cluster():
   """Create a new database cluster with automatic configuration.
   
   This tool handles the complete cluster setup including:
   - Region selection based on compliance requirements
   - Automatic instance sizing based on workload hints
   - Network configuration and security groups
   - Backup schedule and retention policies
   
   WHEN TO USE: Use this when users want to create a database without 
   specifying every detail. The AI will infer appropriate settings from context.
   
   WHEN NOT TO USE: For manual control over all settings, use 
   create_database_cluster_advanced instead.
   
   COMMON MISTAKES TO AVOID:
   - Don't use this if user specifies custom network settings
   - Don't use this for production clusters requiring specific compliance
   """

So we recommend to start with a powerful LLM model but not a weak one. Here's why:

  1. Avoid premature optimization: Debugging tool issues with weaker models wastes time on problems that don't exist with better models
  2. Better error recovery: Powerful models handle edge cases and recover from errors without excessive handholding
  3. Faster iteratio: Simpler descriptions mean less documentation to write and maintain
  4. Production quality: Most production deployments benefit from powerful models anyway

Once your MCP tools work well with a powerful model, you can optionally support smaller models by:

  • Adding detailed descriptions and examples
  • Breaking Agent workflow into simpler, sequential ones

Hybrid Approach: Auto-Generate + Customize

Use auto-generation to validate your concept quickly, then create custom MCP tools for production:

Step 1: Prototype with auto-generation

Follow the guide to convert existing APIs to MCP tools automatically. This works great for rapid iteration, proof-of-concept, and internal tools.

Step 2: Design custom proto definitions for production

When moving to production, create MCP tools from scratch. Design around user tasks, not API endpoints. Use natural language inputs, flat structures, and focused responses. Think: "What does the user want to accomplish?" rather than "What does this API do?"

This gives you development speed early on and production quality when it matters.

Conclusion

Across these four parts, we've shown both the power and limitations of automated API generation for AI Agents. The key is knowing when to use automation and when to invest in custom design.

Share this