Prompting for MCP Tools

Effective prompts lead to better code faster. This chapter covers strategies for communicating tool requirements to AI assistants, from simple requests to complex multi-tool servers.

The Anatomy of a Good Prompt

┌─────────────────────────────────────────────────────────────────────────┐
│                    Effective MCP Tool Prompt                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐    │
│  │ 1. CONTEXT                                                      │    │
│  │    "Create an MCP server for [domain]..."                       │    │
│  │    Sets the problem space and technology                        │    │
│  └─────────────────────────────────────────────────────────────────┘    │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐    │
│  │ 2. CAPABILITY                                                   │    │
│  │    "...with tools that [action] and [action]..."                │    │
│  │    Describes what the server should do                          │    │
│  └─────────────────────────────────────────────────────────────────┘    │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐    │
│  │ 3. CONSTRAINTS                                                  │    │
│  │    "...limit [X], require [Y], return [Z]..."                   │    │
│  │    Sets boundaries and requirements                             │    │
│  └─────────────────────────────────────────────────────────────────┘    │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐    │
│  │ 4. EXAMPLES (Optional)                                          │    │
│  │    "For example, when given [input], return [output]"           │    │
│  │    Clarifies expected behavior                                  │    │
│  └─────────────────────────────────────────────────────────────────┘    │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Prompt Levels

Level 1: Simple Tool Request

For single, straightforward tools:

Create a tool that converts temperatures between Celsius and Fahrenheit.
Input: temperature and source unit.
Output: converted temperature in both units.

What AI generates:

  • Input type with temperature (f64) and unit (enum)
  • Output type with both conversions
  • Validation for reasonable temperature range
  • Unit tests

Level 2: Detailed Tool Request

For tools with specific requirements:

Create a `search_logs` tool that queries application logs.

Input:
- query: String (required) - search pattern (regex supported)
- start_time: DateTime (optional) - earliest log timestamp
- end_time: DateTime (optional) - latest log timestamp
- level: enum [DEBUG, INFO, WARN, ERROR] (optional) - filter by level
- limit: u32 (optional, default 100, max 1000) - result count

Output:
- matches: array of log entries with timestamp, level, message
- total_count: total matches (may exceed limit)
- truncated: boolean indicating if results were limited

Error cases:
- Invalid regex pattern → validation error with pattern location
- Invalid time range (end before start) → validation error
- No logs found → empty result (not an error)

Use chrono for timestamps. Return newest logs first.

What AI generates:

  • Complete input/output types with all fields
  • Regex validation with helpful error messages
  • Time range validation
  • Pagination handling
  • Comprehensive test coverage

Level 3: Server Architecture

For complete server design:

Create a CI/CD MCP server for GitHub Actions.

Tools:
1. list_workflows - Get all workflows for a repo
   - Input: owner, repo
   - Output: workflow id, name, state, path

2. get_workflow_runs - Get recent runs for a workflow
   - Input: owner, repo, workflow_id, status filter (optional)
   - Output: run id, status, conclusion, started_at, duration

3. trigger_workflow - Start a workflow run
   - Input: owner, repo, workflow_id, ref (branch), inputs (map)
   - Output: run id, url
   - IMPORTANT: Require confirmation in description

4. cancel_run - Cancel an in-progress run
   - Input: owner, repo, run_id
   - Output: success boolean

Architecture:
- Use octocrab crate for GitHub API
- Token from GITHUB_TOKEN env var
- Rate limiting: implement retry with backoff
- All times in UTC

Security:
- trigger_workflow must log the action
- No workflow deletion capabilities

Specifying Input Types

Required vs Optional Fields

Create a `send_notification` tool:

Required inputs:
- recipient: String (email or phone)
- message: String (1-1000 chars)

Optional inputs:
- subject: String (for email only)
- priority: enum [LOW, NORMAL, HIGH] (default: NORMAL)
- schedule_at: DateTime (send later, must be future)

AI understands:

  • Option<T> for optional fields
  • Default values via unwrap_or
  • Conditional validation (subject only for email)

Enums and Constraints

Create a `convert_document` tool:

Input format (enum): PDF, DOCX, HTML, MARKDOWN
Output format (enum): PDF, DOCX, HTML, MARKDOWN, TXT

Constraint: Cannot convert to same format (validation error)
Constraint: PDF output only from DOCX, HTML, MARKDOWN

AI generates proper validation:

#![allow(unused)]
fn main() {
if input.source_format == input.target_format {
    return Err(Error::validation("Cannot convert to same format"));
}

if matches!(input.target_format, OutputFormat::Pdf) {
    if !matches!(input.source_format, SourceFormat::Docx | SourceFormat::Html | SourceFormat::Markdown) {
        return Err(Error::validation("PDF output requires DOCX, HTML, or Markdown input"));
    }
}
}

Complex Nested Types

Create a `create_order` tool:

Input:
- customer_id: String
- items: array of:
  - product_id: String
  - quantity: u32 (min 1, max 100)
  - options: optional map of String → String
- shipping_address:
  - street: String
  - city: String
  - state: String (2 letters for US)
  - zip: String
  - country: String (ISO 3166-1 alpha-2)
- payment_method: enum [CREDIT_CARD, PAYPAL, INVOICE]

AI generates proper nested types with validation.

Specifying Output Types

Simple Output

Return temperature in both Celsius and Fahrenheit

Structured Output

Output for get_user tool:
- id: String (UUID)
- email: String
- created_at: DateTime
- profile:
  - display_name: String
  - avatar_url: Option<String>
  - bio: Option<String>
- settings:
  - theme: enum [LIGHT, DARK, SYSTEM]
  - notifications: boolean

Pagination Output

Output for list_items tool:
- items: array of Item objects
- pagination:
  - total: u64 (total matching items)
  - page: u32 (current page, 1-indexed)
  - per_page: u32
  - has_next: boolean
  - next_cursor: Option<String>

Error Handling Guidance

Explicit Error Cases

Handle these error cases for the database query tool:

1. Empty query → Error::validation("Query cannot be empty")
2. Query too long (>10000 chars) → Error::validation with limit info
3. Query timeout (>30s) → Error::internal("Query exceeded timeout")
4. Connection failure → Error::internal with retry suggestion
5. Permission denied → Error::validation("Insufficient permissions for table X")
6. Invalid SQL syntax → Error::validation with position of error

For all errors, include:
- What went wrong
- Why it matters
- How to fix it (if possible)

Error Context

Use .context() for all fallible operations:

Good: .context("Failed to connect to database at {url}")?
Good: .context("Query returned invalid JSON for field 'created_at'")?

Bad: .context("error")?  // Too vague
Bad: ? alone  // No context

Iterating on Generated Code

Refinement Prompts

After initial generation:

The get_weather tool works but:
1. Add caching for 5 minutes (same city returns cached result)
2. Support multiple cities in one call (batch lookup)
3. Add unit tests for cache expiration

Bug Fix Prompts

When something doesn't work:

The search_users tool has an issue:
- Input: { "query": "john", "limit": 10 }
- Expected: Users with "john" in name or email
- Actual: Returns all users

Fix the handler to actually filter by the query parameter.

Performance Prompts

For optimization:

The list_transactions tool is slow for large accounts.

Requirements:
1. Add cursor-based pagination instead of offset
2. Limit results to 100 per call max
3. Add index hint for created_at field
4. Return only id, amount, timestamp (not full transaction)

Domain-Specific Patterns

Database Tools

Create a PostgreSQL MCP server with these patterns:

1. Read-only by default: Only SELECT queries allowed
2. Query timeout: 30 second max
3. Row limit: 1000 rows max (with truncation indicator)
4. Schema filtering: Only show tables matching pattern
5. Sensitive columns: Hide columns named *password*, *secret*, *token*

Use sqlx with connection pooling.

API Integration Tools

Create a Stripe MCP server following these patterns:

1. API key from STRIPE_API_KEY env var
2. Rate limiting: Respect Stripe's rate limits with backoff
3. Pagination: Use Stripe's cursor pagination
4. Idempotency: Add idempotency_key for mutations
5. Webhooks: NOT included (separate concern)

Tools:
- list_customers, get_customer, create_customer
- list_charges, get_charge, create_charge
- list_subscriptions, get_subscription

File System Tools

Create a safe file system MCP server:

Security constraints:
1. Sandbox to specified root directory
2. No path traversal (reject ../.. patterns)
3. No symlink following outside sandbox
4. Max file size: 10MB for read/write
5. No execution of files

Tools:
- list_files: dir contents with type, size, modified
- read_file: contents as text (detect encoding)
- write_file: create/overwrite with content
- delete_file: remove single file (not directories)

Anti-Patterns in Prompting

Too Vague

Bad:

Make a tool that does stuff with data

Good:

Create a tool that parses CSV files and returns rows as JSON

Too Prescriptive

Bad:

Create a struct named DataInput with field data of type Vec<u8>.
Then create a function named process_data that takes DataInput
and returns Result<DataOutput, Error>. The function should first
check if data.len() > 0...

Good:

Create a data processing tool that accepts binary data,
validates it's not empty, and returns the parsed result.

Let AI choose implementation details.

Missing Error Cases

Bad:

Create a tool that divides two numbers

Good:

Create a division tool:
- Input: numerator and denominator (both f64)
- Output: result
- Error: Division by zero should return validation error
- Edge cases: Handle infinity and NaN appropriately

Ambiguous Requirements

Bad:

Create a search tool with good performance

Good:

Create a search tool that:
- Returns results in <100ms for queries under 10 chars
- Supports up to 10,000 items in the search index
- Uses case-insensitive matching
- Returns max 50 results, sorted by relevance

Prompt Templates

New Tool Template

Create a `[tool-name]` tool for [purpose].

Input:
- [field]: [type] ([required/optional]) - [description]
- ...

Output:
- [field]: [type] - [description]
- ...

Error cases:
- [condition] → [error type with message]
- ...

[Additional constraints or requirements]

Tool Modification Template

Update the `[tool-name]` tool:

Current behavior: [what it does now]
Desired behavior: [what it should do]

Changes needed:
1. [Specific change]
2. [Specific change]

Preserve: [what should stay the same]

Bug Fix Template

Fix issue in `[tool-name]`:

Steps to reproduce:
1. [Action]
2. [Action]

Expected: [result]
Actual: [result]

Additional context: [relevant details]

Server Design Template

Create a [domain] MCP server.

Purpose: [what problem it solves]

Tools (list with brief descriptions):
1. [tool_name] - [purpose]
2. [tool_name] - [purpose]

Technical requirements:
- [Dependency/library to use]
- [Configuration approach]
- [Security consideration]

Quality requirements:
- [Coverage, testing, etc.]

Summary

Effective prompting for MCP tools:

AspectApproach
ContextSet domain and technology
CapabilityDescribe what, not how
ConstraintsSet clear boundaries
Error casesEnumerate explicitly
OutputSpecify structure clearly
IterationRefine with focused requests

The key is being specific enough that AI understands intent, while leaving implementation flexibility. Focus on:

  • What the tool should accomplish
  • What inputs it needs
  • What outputs it produces
  • What errors it handles
  • What constraints apply

Let AI handle the Rust implementation details - it knows TypedTool patterns, JsonSchema derives, and error handling conventions.


Continue to Quality Assurance with AI