Writing OpenAPI Specs

LockMCP converts each OpenAPI operation into an MCP tool. The spec is the single source of truth — operation summaries become the tool descriptions the AI reads when deciding what to call, parameter schemas become the tool's typed input fields, and x-mcp-inject markers enable per-user scope injection. A well-written spec directly improves the quality of AI tool calls.

LockMCP accepts OpenAPI 3.0.x YAML or JSON. OpenAPI 2.0 (Swagger) is not supported. Each operationId in your spec becomes one MCP tool — operations without an operationId are skipped during parsing.


What LockMCP reads from your spec

Spec fieldHow LockMCP uses it
operationIdBecomes the MCP tool name. Optional — one is generated from the method and path when absent, but naming it yourself keeps tool names stable across re-uploads.
summaryThe tool's one-line description shown to the AI client. Make it specific.
description (operation level)Extended context appended to the tool description. Use it for caveats, units, and date formats.
parameters[].schemaBecomes the tool's JSON Schema input field.
parameters[].descriptionShown to the AI alongside the field name.
requestBody.content[type].schemaMerged into the tool's input schema as body fields. application/json, vendor +json types and application/x-www-form-urlencoded are supported; allOf, oneOf and anyOf are flattened: for oneOf/anyOf, a field several alternatives declare offers every alternative's values, a field all of them require is required, and a field only some require says so in its description (e.g. "Required when status is APPROVED"). Operations whose only body is multipart/form-data are skipped with a warning.
responses (the first 2xx with a JSON body: 200, then 201, then other 2xx)Becomes the tool's output schema, listed to AI clients so they know what a call returns instead of guessing. example/examples are dropped. A schema over 8 KB is left out with an upload warning — the tool still works. Tools imported earlier get it on their next Re-sync (it shows as "response schema changed"), then publish.
servers[0].urlThe base URL LockMCP proxies calls to. Use your production URL — not localhost.
security + securitySchemesTells LockMCP how to forward credentials. Use bearerAuth for JWT.
x-mcp-inject on a param schemaTriggers role-scoped enum injection — replaced with a per-user enum at call time.

Minimal working spec

The smallest spec that LockMCP can parse has an info block, one server, and at least one operation with an operationId:

yaml
openapi: 3.0.3
info:
  title: My Service API      # shown in the hub dashboard
  version: 1.0.0

servers:
  - url: https://api.myservice.com   # base URL for all proxied calls

security:
  - bearerAuth: []           # forward the user's JWT on every request

paths:
  /items:
    get:
      operationId: listItems        # becomes the MCP tool name
      summary: List all items       # what the AI sees in the tool list
      responses:
        '200':
          description: OK

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

Upload this and LockMCP creates one tool: listItems.


Full annotated example

The sections below walk through a production-quality spec. All examples are drawn from the JHipster sample app spec.

info — spec metadata

yaml
info:
  title: JHipster Sample App API
  description: >
    REST API for the JHipster 9 Spring Boot monolith. Covers self-service
    account management, admin user management, and BankAccount / Label /
    Operation CRUD. All endpoints require a JWT Bearer token obtained from
    the application's login endpoint.
  version: 1.0.0

The title and description are displayed in the hub dashboard. The version field is for your own records and does not affect tool naming.

servers — base URL

yaml
servers:
  - url: http://localhost:8080
    description: Local development

LockMCP proxies every tool call to <servers[0].url> + <path>. Use your public HTTPS URL in production:

yaml
servers:
  - url: https://api.myservice.com
    description: Production
  - url: http://localhost:8080
    description: Local development

Only the first server entry is used for proxying. Additional entries are ignored.

security and securitySchemes

yaml
security:
  - bearerAuth: []   # applies to all operations by default

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

When bearerAuth is declared, LockMCP forwards the authenticated user's JWT as Authorization: Bearer <token> on every proxied call. Your backend validates it exactly as it would any direct client request.

tags — grouping operations

yaml
tags:
  - name: bankAccounts
    description: Bank account CRUD (ROLE_USER)
  - name: admin
    description: Admin user management (ROLE_ADMIN)

Tags group tools in the LockMCP dashboard. Tag each operation with the relevant group:

yaml
paths:
  /api/bank-accounts:
    get:
      tags: [bankAccounts]
      operationId: getAllBankAccounts
      summary: List all bank accounts

paths — one operation = one tool

Each operationId under paths becomes one MCP tool. Write it once per operation:

yaml
paths:
  /api/bank-accounts:
    get:
      tags: [bankAccounts]
      operationId: getAllBankAccounts          # tool name
      summary: List all bank accounts          # AI description
      parameters:
        - name: eagerload
          in: query
          description: Eagerly load associated operations
          schema:
            type: boolean
            default: true
      responses:
        '200':
          description: List of bank accounts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BankAccount'

    post:
      tags: [bankAccounts]
      operationId: createBankAccount
      summary: Create a bank account
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BankAccount'
      responses:
        '201':
          description: Bank account created

components/parameters — reusable parameter refs

Define shared parameters once and $ref them across operations to avoid duplication:

yaml
components:
  parameters:
    Page:
      name: page
      in: query
      description: Zero-based page index
      schema:
        type: integer
        format: int32
        default: 0
        minimum: 0

    Size:
      name: size
      in: query
      description: Number of records per page
      schema:
        type: integer
        format: int32
        default: 20
        minimum: 1
        maximum: 200

Reference them in any operation:

yaml
paths:
  /api/admin/users:
    get:
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/Size'

components/schemas — shared object types

Define request/response bodies as named schemas and compose them with $ref and allOf:

yaml
components:
  schemas:
    AdminUserDTO:
      type: object
      properties:
        id:
          type: integer
          format: int64
        login:
          type: string
          minLength: 1
          maxLength: 50
        email:
          type: string
          format: email
      required:
        - login

    ManagedUserVM:
      description: AdminUserDTO extended with a plaintext password
      allOf:
        - $ref: '#/components/schemas/AdminUserDTO'
        - type: object
          properties:
            password:
              type: string
              minLength: 4
              maxLength: 100

Writing descriptions the AI will use

Claude reads summary and description when deciding which tool to call and how to fill its parameters. Vague text leads to wrong tool selection and malformed calls.

Treat your summary and description fields as prompts. The AI has no other context about what your API does.

summary — one line, action + resource:

AvoidPrefer
Do operationList all bank accounts for the current user
UpdateFully update a bank account by ID
Get dataGet an operation (transaction) by numeric ID

description (operation level) — when to use it and key constraints:

yaml
description: >
  Returns a paginated list of all financial operations (transactions) for
  the authenticated user. Use the `page` and `size` parameters to paginate.
  Dates are ISO-8601 strings (e.g. "2024-03-15T10:00:00Z"). Amounts are
  decimal numbers; negative values represent debits.

parameters[].description — valid values, formats, required constraints:

yaml
- name: sort
  in: query
  description: >
    Sort criteria in the format `property,(asc|desc)`.
    Multiple values allowed. Example: `id,asc` or `date,desc`.
  schema:
    type: string
    example: id,asc

Role-scoped enum injection

For parameters that should be constrained to the specific resource IDs a user can access, add the x-mcp-inject extension to the parameter's schema:

yaml
components:
  parameters:
    BankAccountId:
      name: id
      in: path
      required: true
      description: >-
        Bank account identifier. LockMCP replaces this with an enum of the
        bank-account IDs the current user owns.
      schema:
        type: integer
        format: int64
        x-mcp-inject: "bankAccountIds"   # key name from your bootstrap response

The value of x-mcp-inject is the key the hub looks up in your bootstrap payload. At call time, LockMCP replaces the schema with enum: [<ids the user owns>] and strips the marker — Claude never sees x-mcp-inject.

Common key name patterns:

DomainParameterx-mcp-inject value
Financebank_account_idbankAccountIds
Property managementproperty_idpropertyIds
Project toolsproject_idprojectIds
Healthcareorganisation_idorganisationIds
E-commercestore_idstoreIds

For the full setup — returning the bootstrap payload from your validate response, setting inject keys in the dashboard, and understanding multi-role union behaviour — see Enum Injection.


Uploading to LockMCP

  1. Go to Dashboard → API Specs and click Upload Spec
  2. Paste your YAML or JSON, or drag-and-drop the file
  3. LockMCP parses the spec and lists the operations it found — review the extracted tool names and descriptions
  4. Click Save to store the spec in draft state
  5. Go to Dashboard → Tools, find the tools from your spec, and click Publish to make them available to MCP clients

Re-uploading a spec does not change what your MCP clients receive until you publish.

A re-upload re-parses the spec and records a new draft version for each tool that changed. Your published tools keep serving the version they were already serving — LockMCP never pushes an unreviewed schema change live.

Tools in this state show as Update pending in Dashboard → Tools. Filter by that status, select them, and use Publish to promote them all in one go. (Or promote one at a time in Tool Studio → Versions → Make active.)


Common mistakes

Missing operationId — LockMCP skips operations without an operationId. Every operation that should become a tool must have one. Make them globally unique within the spec (e.g. getAllBankAccounts, not getAll).

servers.url pointing to localhost — Proxied calls will fail in production. Set servers[0].url to your public HTTPS URL before uploading.

Vague summary — "Get data" or "Do thing" causes the AI to pick the wrong tool or ask the user for clarification. Use the pattern: <verb> <resource> [qualifier].

Omitting required: true on path parameters — LockMCP infers path parameters are required, but being explicit prevents ambiguity. Always set required: true on in: path parameters.

requestBody without content — A requestBody with no content key is invalid and will cause a parse error. Always include at least one media type (typically application/json).