How to Design Tools for AI Agents: Workflows vs. API Endpoints
Design AI agent tools around complete user workflows rather than individual API endpoints to minimize context consumption, reduce latency, and provide actionable error recovery.
When building AI agents that interact with external services, the way you structure tools determines whether your agent performs efficiently or struggles with fragmented logic. According to the ComposioHQ/awesome-codex-skills repository, successful tool design requires packaging entire workflows into single, high-impact operations rather than exposing raw API endpoints. This workflow-centric approach ensures agents stay within their context budget while delivering reliable, human-oriented functionality.
Why Workflow-Centric Tools Outperform Raw API Endpoints
AI agents interact with external services through tools—tiny, purpose-built operations that the model invokes automatically. A well-designed tool does more than wrap a raw API call; it packages the entire user-level workflow the agent needs to accomplish.
Plain API-endpoint tools create four critical problems that workflow-centric designs solve:
-
Fragmented calls force the agent to orchestrate several endpoints (e.g., list calendars → find free slot → create event). A workflow tool collapses this into a single
schedule_eventoperation that checks availability and creates the event in one call. -
Context-budget waste occurs when every extra call consumes tokens and context space, increasing latency. Workflow tools provide compact interactions where the agent sends one request and receives one concise response.
-
Error handling complexity requires the model to recover from partial failures across many calls. Workflow tools return centralized, actionable errors (e.g., "No free slots; try a different date" instead of HTTP 404 codes).
-
Inconsistent naming plagues endpoint names that do not match how humans think about tasks. Workflow tools use human-oriented naming that reflects natural verbs (
schedule_event,analyze_invoice).
The MCP Builder guide in mcp-builder/SKILL.md explicitly warns against merely wrapping endpoints and urges developers to "Build for Workflows, Not Just API Endpoints". It stresses that tools should be action-oriented, concise, and aligned with the agent’s limited context.
Core Design Principles for AI Agent Tools
According to reference/mcp_best_practices.md, effective tool design follows a structured workflow-first methodology:
-
Identify the user’s goal—start from the task the agent is asked to perform (e.g., "book a meeting").
-
Map the end-to-end workflow—list every sub-step required to achieve that goal.
-
Collapse the workflow into a single tool when possible, exposing only the parameters the user would naturally provide.
-
Design the tool’s signature with clear, typed inputs using Pydantic for Python or Zod for TypeScript, and include an optional
response_formatargument (json|markdown). -
Provide actionable error messages that guide the model toward the next step instead of dumping raw HTTP error codes.
-
Respect pagination, character limits, and context budget—return truncated results with helpful "see more" hints.
These guidelines are reinforced in the Tool Design Guidelines section of reference/mcp_best_practices.md and the Tool Naming rules in reference/python_mcp_server.md.
Implementing Workflow-First Tools in Practice
Below are two illustrative implementations that embody the workflow-first mindset from the repository's reference documentation.
Python Example: schedule_event
This tool encapsulates availability lookup and event creation in a single operation, as recommended in reference/python_mcp_server.md.
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field
mcp = FastMCP("calendar_mcp")
class ScheduleEventInput(BaseModel):
title: str = Field(..., description="Event title")
participants: list[str] = Field(..., description="List of email addresses")
start_time: str = Field(..., description="ISO‑8601 start time")
duration_minutes: int = Field(..., ge=1, description="Length of the meeting")
@mcp.tool(
annotations={
"title": "Schedule Event",
"readOnlyHint": False,
"destructiveHint": True,
"idempotentHint": False,
"openWorldHint": True,
}
)
async def schedule_event(input: ScheduleEventInput, response_format: str = "markdown"):
"""
Checks participants' availability and creates a calendar event.
Returns a concise confirmation or a helpful error message.
"""
# 1️⃣ Check free slots (internal helper)
free = await check_availability(input.participants, input.start_time, input.duration_minutes)
if not free:
return {
"isError": True,
"content": [
{
"type": "text",
"text": "No common free slot found. Suggest a later time or fewer participants."
}
]
}
# 2️⃣ Create the event (single API call)
event = await calendar_api.create_event(
title=input.title,
start=input.start_time,
duration=input.duration_minutes,
attendees=input.participants,
)
# 3️⃣ Return in requested format
if response_format == "json":
return {"event_id": event.id, "link": event.join_url}
else:
return {
"content": [
{
"type": "text",
"text": f"✅ Event **{input.title}** scheduled for {input.start_time}.\n"
f"Link: {event.join_url}"
}
]
}
The signature only asks for information a user would naturally provide, and errors are actionable ("suggest a later time") rather than technical HTTP codes.
TypeScript Example: analyze_invoice
This implementation from reference/node_mcp_server.md demonstrates the fetch-validate-summarize workflow pattern.
import { Server } from "mcp-sdk";
import { z } from "zod";
const server = new Server({
name: "finance-mcp",
version: "1.0.0",
});
const AnalyzeInvoiceInput = z.object({
invoiceId: z.string().describe("Unique identifier of the invoice to analyze"),
includeDetails: z.boolean().default(false).describe("Return line‑item breakdown?"),
});
server.registerTool({
name: "analyze_invoice",
description: "Fetches an invoice, validates totals, extracts key metrics, and optionally returns line‑item details.",
inputSchema: AnalyzeInvoiceInput,
annotations: {
title: "Analyze Invoice",
readOnlyHint: true,
destructiveHint": false,
idempotentHint: true,
openWorldHint: false,
},
async handler({ invoiceId, includeDetails }, context) {
// 1️⃣ Retrieve invoice from external billing API (single call)
const invoice = await billingApi.getInvoice(invoiceId);
// 2️⃣ Validate totals & compute metrics
const total = invoice.items.reduce((sum, i) => sum + i.amount, 0);
const discrepancy = Math.abs(total - invoice.totalAmount);
// 3️⃣ Build response
const base = {
status: discrepancy < 0.01 ? "valid" : "mismatch",
total,
currency: invoice.currency,
};
if (includeDetails) {
return {
response_format: "json",
data: { ...base, items: invoice.items },
};
}
// Human‑readable Markdown by default
return {
content: [
{
type: "text",
text: `**Invoice ${invoiceId}** – ${base.status}\n` +
`Total: ${base.total} ${base.currency}\n` +
(base.status === "mismatch"
? `⚠️ Discrepancy of ${discrepancy.toFixed(2)} ${base.currency}`
: "✅ Totals match."),
},
],
};
},
});
The optional includeDetails flag lets the model request extra data only when needed, conserving the token budget while supporting both JSON for downstream processing and Markdown for direct user presentation.
Summary
-
Package workflows, not endpoints: Collapse multi-step processes (availability checks + creation) into single tool calls to preserve LLM context budgets.
-
Use human-oriented naming: Name tools after user goals (
schedule_event) rather than API operations (post_calendar_events). -
Implement strict typing: Use Pydantic (Python) or Zod (TypeScript) to define clear input schemas with descriptive fields.
-
Return actionable errors: Guide the agent toward resolution with human-readable messages rather than raw HTTP status codes.
-
Support flexible response formats: Allow
jsonfor structured data andmarkdownfor human-readable output via optional parameters. -
Annotate tool behavior: Use
readOnlyHint,destructiveHint,idempotentHint, andopenWorldHintannotations to help the LLM understand side effects.
Frequently Asked Questions
What is the difference between a workflow tool and an API endpoint tool?
A workflow tool encapsulates an entire user task—such as checking availability and creating a calendar event—into a single callable operation. An API endpoint tool merely wraps one raw HTTP endpoint, forcing the AI agent to chain multiple calls and handle intermediate state manually. According to mcp-builder/SKILL.md, workflow tools reduce latency and error rates by handling orchestration internally.
How should I handle errors when designing tools for AI agents?
Design error messages that suggest the next action rather than reporting technical failures. For example, return "No common free slot found. Suggest a later time or fewer participants" instead of HTTP 404 or 500 codes. This approach, documented in reference/mcp_best_practices.md, enables the LLM to recover gracefully without consuming additional context tokens on debugging.
Why should I use Pydantic or Zod for tool input schemas?
Pydantic (Python) and Zod (TypeScript) provide runtime validation and clear type definitions that help the LLM understand exactly what parameters to provide. These libraries allow you to add field descriptions, constraints, and defaults that translate directly into the tool's interface description seen by the agent. The reference/python_mcp_server.md and reference/node_mcp_server.md files mandate these libraries for production MCP server implementations.
How do I manage context budget constraints when designing tools?
Respect the LLM's limited context window by returning concise responses and supporting pagination hints. Include optional flags (like includeDetails) that let the agent request additional data only when necessary. The Tool Design Guidelines in reference/mcp_best_practices.md recommend truncating long results with "see more" hints and avoiding verbose intermediate data in favor of final summaries.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →