Designing Composable Tools for Enterprise MCP: From Theory to Practice

Published: (December 23, 2025 at 12:35 PM EST)
7 min read
Source: Dev.to

Source: Dev.to

Enterprise MCP: From “API Gateway” to Composable Skills‑Based Tool Design

In my previous post, I discussed how the biggest gap in enterprise MCP implementations isn’t the protocol itself—it’s the architectural decisions around it. Specifically, teams often treat MCP as an “API gateway for LLMs” when they should be thinking about composable tool design.

Today, I want to show you what composable, skills‑based tool design actually looks like in practice.

Hotel Operations Case Study

Hotel Operations Case Study

Let’s start with a real scenario from a hotel management system. A front‑desk employee says:

“Beth Gibbs is checking out, and she says the toilet in her room is broken.”

This simple interaction requires:

  • Processing the checkout (payment, receipts, room status)
  • Filing a maintenance request (with room context intact)
  • Updating inventory and availability
  • Routing the request to the right maintenance team

How would you design MCP tools for this?

The Naïve Approach

Many (if not most) teams start by exposing existing APIs as MCP tools:

[
  "get_guest_by_email",
  "get_booking_by_guest",
  "get_room_by_booking",
  "create_payment_intent",
  "charge_payment_method",
  "send_receipt_email",
  "update_booking_status",
  "update_room_status",
  "create_case",
  "assign_case_to_contact",
  "set_case_priority"
]

An agent now has to orchestrate 11+ API calls in the correct sequence, handle potential failures at each step, and maintain state throughout. The result? Slow, error‑prone, and terrible user experiences.

The Compositional Approach

What if, instead, we designed tools around user intent? The calls could look something like:

[
  "process_guest_checkout",
  "submit_maintenance_request"
]

Two tools. One natural conversation. The complexity hasn’t disappeared—it’s just moved to where it belongs.

Nine Patterns for Composable, Skills‑Based Tool Design

After implementing production MCP systems, these are the patterns that separate elegant architectures from fragile ones.

1. Accept Business Identifiers, Not System IDs

Bad

{
  "contact_id": "003Dn00000QX9fKIAT",
  "booking_id": "a0G8d000002kQoFEAU",
  "room_id":   "a0I8d000001pRmXEAU"
}

Good

{
  "guest_email": "beth.gibbs@email.com",
  "room_number": "302"
}

Let the backend resolve human‑readable identifiers to internal IDs. The agent shouldn’t need to know your database schema.

Tip: This applies to all tool parameters—not just the primary entity. When updating relationships (e.g., reassigning a case to a different room), continue using business identifiers:

{
  "idempotency_token": "550e8400-e29b-41d4-a716-446655440000",
  "room_number": "402",               // backend resolves to room_id
  "guest_email": "new.guest@example.com" // backend resolves to contact_id
}

The agent should never need to call get_room_by_number or get_guest_by_email just to obtain IDs for another operation. Every tool parameter should use business identifiers, and the backend handles all ID resolution internally.

2. Build Idempotency Into Tool Design

Every tool that creates or modifies resources should accept an idempotency token:

{
  "idempotency_token": "550e8400-e29b-41d4-a716-446655440000",
  "guest_email": "beth.gibbs@email.com",
  "description": "Toilet broken in room 302"
}

When the agent retries (and it will), the backend recognizes the duplicate request and returns the original result. Idempotency is a backend responsibility, not an agent responsibility.

Added benefit: For multi‑system operations (e.g., a checkout process spanning payment processing and CRM updates), idempotency tokens enable saga‑pattern orchestration. If a payment succeeds but a CRM update fails, the backend can use the token to coordinate compensating transactions (like refunding the payment) without agent involvement.

3. Coordinate State Transitions Atomically

When a guest checks in, multiple things must happen together:

  • Booking status: Reserved → Checked In
  • Room status: Available → Occupied
  • Opportunity stage: Pending → Active

These shouldn’t be three separate tools the agent must coordinate. One tool (check_in_guest) should orchestrate the entire state transition atomically.

4. Embed Authorization in Tool Design

Instead of exposing unrestricted search tools:

[
  "search_all_cases",
  "search_all_rooms",
  "search_all_bookings"
]

Design tools with appropriate scope:

[
  "search_cases_on_behalf_of_guest(guest_email)",
  "search_rooms_on_behalf_of_guest(guest_email)",
  "search_rooms_on_behalf_of_staff(floor_filter, status_filter)"
]

The tool interface itself encodes who can see what. Authorization becomes declarative rather than imperative.

5. Provide Smart Defaults

Whenever possible, reduce the agent’s cognitive load:

{
  "guest_email": "required",
  "check_in_date": "defaults to today",
  "number_of_guests": "defaults to 1",
  "status_filter": "defaults to 'Open'"
}

Agents should only need to specify what’s genuinely variable.

6. Document Prerequisites and Errors

(Content truncated in the original snippet – be sure to include a clear list of required pre‑conditions for each tool and a concise error‑handling guide.)

7. Support Partial Updates with Clear Semantics

Update operations should be easy to reason about:

{
  "external_id": "required",
  "check_in_date": "optional – only changes if provided",
  "room_number": "optional – only changes if provided",
  "guest_email": "optional – only changes if provided"
}

“Only provide fields to change—rest preserved.”
This is much simpler than forcing the agent to read‑modify‑write.

8. Create Defensive Composition Helpers

Some operations need prerequisites. Rather than forcing the agent to check‑then‑create:

# idempotent helper
create_contact_if_not_found(email, first_name, last_name)

This helper can be safely called by orchestration tools to ensure prerequisites exist.

9. Design for Natural Language Patterns

Listen to how people actually talk:

Natural languageTool name
“Check in Beth Gibbscheck_in_guest
“Room 302’s toilet is broken”submit_maintenance_request
“Move the booking to room 402manage_bookings

Tool names and parameters should match the language users naturally employ.

TL;DR

  • Design tools around user intent, not low‑level API calls.
  • Use business identifiers and let the backend resolve internal IDs.
  • Make every mutating tool idempotent and capable of atomic state changes.
  • Encode authorization and sensible defaults directly in the tool signatures.

By following these patterns, you’ll turn MCP from a brittle “glue layer” into a robust, composable skill set that lets LLM agents act like true domain experts. 🚀

r Modes

Tool descriptions should guide the agent toward success

Check‑in tool:

“Validates guest/reservation prerequisites, checks room vacancy, executes state transitions. Returns booking and room details or error codes (404: guest/reservation not found, 409: multiple reservations or room unavailable).”

When the agent knows the failure modes upfront, it can handle them gracefully or ask clarifying questions before attempting the operation.

The Architecture Behind Composable Tools

These nine patterns emerge from a single architectural principle: let LLMs handle intent, let backends handle execution.

  • LLMs – probabilistic systems optimized for understanding human communication.
  • Backends – deterministic systems optimized for reliable state management and transactional consistency.

When you blur this boundary—asking LLMs to orchestrate multi‑step operations or programming backends to parse natural language—you end up with systems that are neither reliable nor intelligent.

How the patterns map

Pattern groupWhat it enables
1, 5, 9 – Business identifiers, smart defaults, natural‑language alignmentLLM works with human concepts; system‑level details go to the backend.
2, 3, 6 – Idempotency, atomic transitions, error modesBackend guarantees reliability; LLM doesn’t need to reason about retries or failure recovery.
4, 7, 8 – Authorization scope, partial updates, defensive helpersTool interfaces encode business rules; backend validates and enforces constraints.

Architectural payoff

When backends handle orchestration (good design):

  • One implementation, tested and proven
  • Transactional consistency guaranteed
  • Observable state transitions
  • Reusable across interfaces (web, mobile, MCP)

When LLMs handle orchestration (poor design):

  • Logic scattered across conversations
  • Non‑deterministic coordination
  • Opaque failures (hard to debug)
  • Context bloat (e.g., 50+ tools, 6+ calls per task)

Real‑World Impact

While building the Dewy Resort application, we iteratively replaced direct API calls and API‑tool wrappers with a skills‑based architectural design. The benchmarks below illustrate the difference.

MetricBefore composable designAfter composable design
Average response time8–12 seconds2–4 seconds
Success rate73 %94 %
Number of tools4712
Avg. tool calls per interaction6.21.8
User feedback“It works, but it’s slow and sometimes gets confused.”“It just works.”

The difference isn’t the LLM—it’s the architecture.

Implementation Checklist for Enterprise MCP Tool Design

When designing your MCP tools for production systems, ask yourself:

Identity & Resolution

  • Do tools accept business identifiers (email, name, number)?
  • Does the backend handle ID resolution?

Safety & Reliability

  • Do creation tools require idempotency tokens?
  • Are state transitions atomic?
  • Are prerequisites validated before operations?

Authorization & Access

  • Do tools encode authorization scope in their interface?
  • Are search tools scoped to appropriate contexts?

Cognitive Load

  • Do tools provide sensible defaults?
  • Are tool names aligned with natural language?
  • Do descriptions document error modes?

Flexibility

  • Do update operations support partial updates?
  • Can agents modify relationships using business identifiers?

The Broader Pattern

These patterns apply anywhere you’re building AI agents that interact with enterprise systems and processes.

DomainIntent‑driven example
Healthcare“Schedule a follow‑up for this patient” → orchestrates appointment booking, notification, record update.
Finance“File this expense report” → handles validation, approval routing, accounting entries.
Retail“Process this return” → coordinates inventory, refunds, customer notifications.

The question is always the same: Are you designing tools around user intent, or around API operations?

Conclusion

Enterprise MCP gives you the foundation for tool interoperability. Composable, skills‑based design is how you build something useful on that foundation.

The protocol won’t save you from bad architecture, but good architecture—tools composed around user intent—will.

## Intent‑Driven Architecture: From Curiosity to Production‑Grade

> **Stop wrapping APIs. Start composing skills.**  

Your users will thank you. Your agents will thank you. *(Ok, your agents probably won't.)* But your operations team will definitely thank you.

### What’s your experience with MCP tool design?

I’d love to hear what patterns you’re discovering. Drop a comment or reach out on LinkedIn—the more we share these patterns, the faster we’ll all build better AI systems.

*This post builds on [Beyond Basic MCP: Why Enterprise AI Needs Composable Architecture](https://dev.to/zaynelt/beyond-basic-mcp-why-enterprise-ai-needs-composable-architecture-273k), where I explored the architectural principles that make MCP useful in production.*
Back to Blog

Related posts

Read more »