Writing Effective Tool Descriptions

Complete information upfront = LLMs work efficiently without probing.

The Problem

Bad tool descriptions = LLMs waste time probing and guessing. A wrapper can be technically perfect, but if the tool descriptions are vague, LLMs will make multiple failed attempts, ask users for clarification, and misinterpret responses.

Best Practice: Always Include test_connection

ALWAYS make test_connection your first tool. It provides:

  • Quick validation that credentials work
  • Network connectivity check
  • Minimal resource usage (typically limit=1)
  • Clear debugging when things fail
{
  "name": "test_connection",
  "description": "GET /endpoint?limit=1 - Test API connection and authentication\n\nMinimal call to verify credentials and connectivity\n\nRESPONSE: Single record on success\nERROR: 401 for auth failure, timeout for network issues\n\nUSE THIS FIRST to verify setup is working",
  "endpoint": "/appropriate-endpoint",
  "method": "GET",
  "input_schema": {
    "type": "object",
    "properties": {}
  }
}

Bad vs Good Descriptions

❌ BAD - Vague Description

{
  "description": "GET /customers - List customers"
}
// Result: LLM doesn't know pagination format, defaults, limits, or response structure

✅ GOOD - Complete Description

{
  "description": "GET /crm/v3/objects/contacts - List paginated contacts\n\nRESPONSE SHAPE:\nSuccess: {\"results\": [array of contacts], \"paging\": {\"next\": {\"after\": \"cursor\", \"link\": \"url\"}}}\nEmpty: {\"results\": []} with no paging field\nNever returns bare array - always wrapped in object\n\nDEFAULT BEHAVIOR:\n- limit: 10 (if not specified)\n- archived: false (only active contacts)\n- properties: Returns only firstname, lastname, email, createdate, lastmodifieddate, hs_object_id\n- after: Omit for first page\n\nRATE LIMITS: 10 req/sec, 100 req per 10 sec burst, ~250k/day\n\nPAGINATION:\n- Use returned paging.next.after for next page\n- Last page: no paging field in response\n- Cursor may expire if not used promptly\n\nERROR RESPONSES:\n- 401: {\"status\":\"error\", \"message\":\"Authentication required\", \"category\":\"AUTHENTICATION_ERROR\"}\n- 429: {\"status\":\"error\", \"message\":\"You have reached your limit\", \"category\":\"RATE_LIMIT\"}\n- 400: {\"status\":\"error\", \"message\":\"Error validating request\", \"errors\":[...], \"category\":\"VALIDATION_ERROR\"}"
}

What to Include in Every Description

Response Shape

  • • Success response structure
  • • Empty result format
  • • Whether array or object
  • • Field names and types

Default Behavior

  • • Default parameter values
  • • What happens when omitted
  • • Which fields are returned
  • • Sort order defaults

Rate Limits

  • • Requests per second
  • • Burst limits
  • • Daily quotas
  • • Retry behavior

Pagination

  • • Cursor format
  • • How to get next page
  • • Last page indicators
  • • Cursor expiration

Error Responses

  • • 401 authentication format
  • • 429 rate limit format
  • • 400 validation errors
  • • 404 not found format

Special Behavior

  • • Quirks and gotchas
  • • Undocumented features
  • • Version differences
  • • Edge cases

Research-First Approach

Before creating any MCP wrapper, conduct deep API research to gather complete information. Use this template to guide your research:

# API Deep Research Request for [SERVICE NAME]

I need you to gather COMPLETE information about the [SERVICE] API to create perfect MCP tool descriptions. Please run actual API calls and document the EXACT behavior.

## 1. Parameter Discovery (CRITICAL!)
For EACH endpoint, I need you to discover:

### Test with REAL API calls:
```bash
# Example: What happens with different parameters?
curl -X GET "https://api.example.com/items"
curl -X GET "https://api.example.com/items?limit=5"
curl -X GET "https://api.example.com/items?limit=0"
curl -X GET "https://api.example.com/items?limit=999999"
curl -X GET "https://api.example.com/items?invalid_param=test"
```

Document:
- **Exact parameter names** (is it 'message', 'msg', 'text', or 'body'?)
- **What happens when omitted** (default values)
- **Validation rules** (what makes it fail?)
- **Limits** (max length, min/max values)
- **Special formats** (date format, phone format)

## 2. Response Shape Testing
Run these scenarios and capture EXACT responses:

```bash
# Success case with data
curl -X GET "https://api.example.com/items?limit=2" | jq

# Empty result case
curl -X GET "https://api.example.com/items?filter=nonexistent" | jq

# Error cases
curl -X GET "https://api.example.com/items?limit=invalid" | jq
curl -X POST "https://api.example.com/items" -d '{}' | jq  # Missing required fields
curl -X GET "https://api.example.com/items/99999999" | jq  # Not found
```

Provide the COMPLETE response for each, not summaries.

Parameter Discovery

Test with REAL API calls to discover exact behavior:

Exact Parameter Names

Is it 'message', 'msg', 'text', or 'body'? Test to find out exactly.

Default Values

What happens when parameters are omitted? Document the defaults.

Validation Rules

What makes requests fail? Test boundary conditions and invalid inputs.

Format Requirements

Date formats, phone number formats, special encoding requirements.

Common Pitfalls to Avoid

❌ Assuming Documentation is Complete

Official docs often miss edge cases, undocumented parameters, and actual behavior.

❌ Not Testing Error Cases

LLMs need to know exact error formats to handle failures gracefully.

❌ Vague Pagination Instructions

"Use cursor for next page" isn't enough - show exact cursor location and format.

❌ Missing Rate Limit Info

LLMs can't optimize without knowing rate limits and quotas.

Real-World Example: HubSpot Contacts

Complete Tool Description

Endpoint: GET /crm/v3/objects/contacts

Response Success:

{"results": [...], "paging": {"next": {"after": "cursor"}}}

Response Empty:

{"results": []} (no paging field)

Defaults:

  • limit: 10
  • archived: false
  • properties: firstname, lastname, email, createdate, lastmodifieddate, hs_object_id

Rate Limits:

  • 10 requests/second
  • 100 requests per 10 second burst
  • ~250k requests/day

Pagination:

  • Use paging.next.after for next page
  • Last page has no paging field
  • Cursor expires after ~10 minutes

The Result

With complete tool descriptions, LLMs can:

  • ✅ Make successful API calls on the first attempt
  • ✅ Handle errors appropriately without retrying blindly
  • ✅ Optimize for rate limits automatically
  • ✅ Navigate pagination efficiently
  • ✅ Provide accurate feedback to users
  • ✅ Work with the API as effectively as a human developer