tutorials2026-02-2412 min read

How to Make Your OpenAPI Spec MCP-Friendly

Your OpenAPI spec is the source of truth for MCP tool generation. Good names, clear descriptions, and structured schemas directly determine how well AI agents discover and use your tools. Here's how to get it right.

E

Emcy Team

Engineering

Side-by-side comparison of a vague OpenAPI spec versus a well-described MCP-friendly spec

When you generate an MCP server from an OpenAPI spec, every operationId becomes a tool name, every summary becomes a tool description, and every parameter becomes a tool input. The AI agent sees only what's in the spec. If the spec is vague, the agent guesses. If the spec is precise, the agent acts correctly.

This post covers the concrete properties in your OpenAPI spec that affect MCP tool quality—and how to set them in the frameworks you're already using.

Why This Matters

MCP tool selection works by matching a user's intent to the best tool. Whether the selection uses an LLM reading tool definitions or semantic vector search, the same inputs matter:

  1. Tool name (from operationId) — must be unique, descriptive, and action-oriented
  2. Tool description (from summary + description) — must explain what the tool does and when to use it
  3. Parameter descriptions — must tell the agent what values are expected
  4. Response schema — helps the agent understand what it will get back

Get these right and your MCP server works out of the box. Get them wrong and the agent calls the wrong tools, passes bad parameters, and hallucinates missing context.

1. Use Descriptive operationId Values

The operationId becomes the MCP tool name. AI agents use it as a primary signal for tool selection. A good operation ID reads like a function name—verb first, noun second, specific enough to distinguish from siblings.

Bad:

paths:
  /users:
    get:
      operationId: getUsers
  /users/{id}:
    get:
      operationId: getUser
    put:
      operationId: updateUser
    delete:
      operationId: deleteUser

This is fine for a REST API, but getUser and getUsers are almost identical to an embedding model. The agent may pick the wrong one.

Good:

paths:
  /users:
    get:
      operationId: list_all_users
  /users/{id}:
    get:
      operationId: get_user_by_id
    put:
      operationId: update_user_profile
    delete:
      operationId: deactivate_user_account

Use underscores, not camelCase—most MCP clients normalize to snake_case anyway. Be specific: update_user_profile is better than updateUser because it tells the agent what gets updated.

Here's how to set the operationId in your framework:

from fastapi import FastAPI

app = FastAPI()

@app.get("/users", operation_id="list_all_users")
async def list_users():
    """List every user in the agent."""
    ...

@app.get("/users/{user_id}", operation_id="get_user_by_id")
async def get_user(user_id: str):
    """Retrieve a single user by their unique ID."""
    ...

2. Write Summary and Description for the AI, Not Just Humans

The summary field is short (one line) and becomes the primary tool description. The description field can be longer and should explain when to use this endpoint and what it returns. Think of it as writing a docstring for an AI coworker.

paths:
  /invoices:
    get:
      operationId: search_invoices
      summary: Search invoices by date range, status, or customer
      description: >
        Returns a paginated list of invoices matching the given filters.
        Use this when the user asks about billing, payments, or outstanding
        balances. Supports filtering by date_from, date_to, status
        (draft, sent, paid, overdue), and customer_id. Returns at most
        50 results per page.

Tips for writing MCP-friendly descriptions:

  • Start the summary with a verb: "Search," "Create," "Delete," "List"
  • In the description, mention user intents that should trigger this tool ("Use this when the user asks about...")
  • Mention important constraints: pagination limits, required permissions, side effects
  • Avoid internal jargon the model won't understand

Here's how to write rich descriptions in each framework:

from fastapi import FastAPI, Query

app = FastAPI()

@app.get(
    "/invoices",
    operation_id="search_invoices",
    summary="Search invoices by date range, status, or customer",
    description=(
        "Returns a paginated list of invoices matching the given filters. "
        "Use this when the user asks about billing, payments, or outstanding "
        "balances. Supports filtering by date_from, date_to, status "
        "(draft, sent, paid, overdue), and customer_id."
    ),
)
async def search_invoices(
    status: str = Query(
        None,
        description="Filter by invoice status: draft, sent, paid, or overdue"
    ),
    customer_id: str = Query(
        None,
        description="Filter invoices to a specific customer by their ID"
    ),
):
    ...

3. Describe Every Parameter

Every parameter without a description is a guessing game for the AI. The agent will try to infer the meaning from the name alone—and names like q, type, or filter are ambiguous.

Bad:

parameters:
  - name: q
    in: query
    schema:
      type: string
  - name: type
    in: query
    schema:
      type: string

Good:

parameters:
  - name: q
    in: query
    description: >
      Full-text search query. Matches against invoice number,
      customer name, and line item descriptions.
    schema:
      type: string
  - name: type
    in: query
    description: >
      Invoice type filter. Use "recurring" for subscription invoices,
      "one-time" for single charges.
    schema:
      type: string
      enum: [recurring, one-time]

Key rules:

  • Always add description to every parameter, even if the name seems self-explanatory
  • Use enum when there's a fixed set of values—the agent will use exactly these values instead of guessing
  • Specify format for strings: date, date-time, email, uuid. This helps the agent format values correctly
  • Add example values—some MCP clients surface these as hints

4. Use enum Aggressively

Enums are the single most impactful thing you can add for MCP tool quality. When a parameter has an enum, the agent picks from the list instead of inventing values.

parameters:
  - name: status
    in: query
    description: Filter by invoice status
    schema:
      type: string
      enum:
        - draft
        - sent
        - paid
        - overdue
  - name: sort_by
    in: query
    description: Field to sort results by
    schema:
      type: string
      enum:
        - created_at
        - due_date
        - amount
        - customer_name
      default: created_at
  - name: sort_order
    in: query
    description: Sort direction
    schema:
      type: string
      enum:
        - asc
        - desc
      default: desc

Without enums, the agent might pass status=active (which doesn't exist) or sort=date (wrong field name). With enums, it can only pass valid values.

5. Define Response Schemas

Response schemas help the agent understand what data it will get back and how to present it to the user. Without a response schema, the agent has to parse the raw JSON and guess what each field means.

responses:
  "200":
    description: List of matching invoices
    content:
      application/json:
        schema:
          type: object
          properties:
            data:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    description: Unique invoice identifier
                  invoice_number:
                    type: string
                    description: Human-readable invoice number (e.g., INV-2026-0042)
                  status:
                    type: string
                    enum: [draft, sent, paid, overdue]
                    description: Current invoice status
                  total_amount:
                    type: number
                    description: Total amount in cents
                  currency:
                    type: string
                    description: ISO 4217 currency code
            total_count:
              type: integer
              description: Total number of invoices matching the filters
            has_more:
              type: boolean
              description: Whether there are more results beyond this page

The description on each response field matters. total_amount described as "Total amount in cents" prevents the agent from displaying $15000 instead of $150.00.

6. Tag and Group Your Operations

Tags control how tools are grouped. When you have dozens of endpoints, meaningful tags help both semantic search and LLM-based selection narrow down the relevant tools.

tags:
  - name: invoices
    description: Create, search, update, and manage invoices
  - name: customers
    description: Manage customer records, contacts, and preferences
  - name: payments
    description: Process payments, refunds, and view transaction history

paths:
  /invoices:
    get:
      tags: [invoices]
      operationId: search_invoices
      ...
  /customers:
    get:
      tags: [customers]
      operationId: list_customers
      ...

Some MCP server generators (including Emcy) use tags to namespace tools or provide filtering. Consistent, meaningful tags make this work well.

7. Mark Dangerous Operations Clearly

If an endpoint has side effects—deletes data, sends emails, charges money—say so in the description. MCP clients like Claude will be more cautious with tools that are clearly marked as destructive.

/invoices/{id}:
  delete:
    operationId: permanently_delete_invoice
    summary: Permanently delete an invoice and all associated data
    description: >
      WARNING: This permanently deletes the invoice, its line items,
      and all payment records. This action cannot be undone. Only use
      this when the user explicitly confirms deletion. Consider using
      archive_invoice instead for soft deletion.

The word "permanently" in the operation ID and "cannot be undone" in the description signal to the AI that it should confirm with the user before calling this tool.

Checklist

Before importing your OpenAPI spec into an MCP server generator, verify:

PropertyCheck
operationIdUnique, snake_case, verb-first, specific
summaryOne-line, starts with a verb, explains the action
descriptionMentions user intents, constraints, and side effects
ParametersEvery parameter has a description
EnumsFixed-value parameters use enum
Response schemaDefined with field-level descriptions
TagsMeaningful grouping with tag descriptions
Destructive opsClearly marked in name and description

A Complete Example

Putting it all together—here's what a well-documented endpoint looks like:

openapi: "3.1.0"
info:
  title: Acme Billing API
  version: "1.0.0"
  description: >
    API for managing invoices, customers, and payments.
    Used by the billing team and automated systems.

paths:
  /invoices:
    get:
      operationId: search_invoices
      tags: [invoices]
      summary: Search invoices by date range, status, or customer
      description: >
        Returns a paginated list of invoices matching the given filters.
        Use this when the user asks about billing, payments, or outstanding
        balances. Returns at most 50 results per page. Results are sorted
        by creation date descending by default.
      parameters:
        - name: status
          in: query
          description: "Filter by invoice status: draft, sent, paid, or overdue"
          schema:
            type: string
            enum: [draft, sent, paid, overdue]
        - name: customer_id
          in: query
          description: Filter invoices for a specific customer
          schema:
            type: string
            format: uuid
        - name: date_from
          in: query
          description: Start of date range filter (inclusive, ISO 8601)
          schema:
            type: string
            format: date
            example: "2026-01-01"
        - name: date_to
          in: query
          description: End of date range filter (inclusive, ISO 8601)
          schema:
            type: string
            format: date
        - name: page
          in: query
          description: Page number for pagination (starts at 1)
          schema:
            type: integer
            default: 1
            minimum: 1
      responses:
        "200":
          description: Paginated list of matching invoices
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Invoice"
                  total_count:
                    type: integer
                    description: Total matching invoices
                  has_more:
                    type: boolean

Every field has a purpose. The agent knows what the tool does, when to use it, what parameters to pass, and what it will get back.


Building MCP servers from OpenAPI specs? Try Emcy—we generate production-ready MCP servers with OAuth, tool selection, and telemetry built in.

Tags
OpenAPI
MCP
best practices
API design
Claude
tool selection