Skip to main content
The OpenAPI Adapter automatically converts REST API endpoints defined in an OpenAPI 3.x specification into fully-functional MCP tools. This guide walks you through adding the adapter to your app.
Prerequisites:
  • A FrontMCP project initialized (see Installation)
  • An OpenAPI 3.x specification (URL or local file)
  • Basic understanding of REST APIs

What You’ll Build

By the end of this guide, you’ll have:
  • ✅ An app that automatically generates tools from an OpenAPI spec
  • ✅ Type-safe input validation for all API endpoints
  • ✅ Authentication configured for API requests
  • ✅ Tools that inherit all your app-level plugins and providers
The OpenAPI Adapter is perfect for quickly exposing REST APIs to AI agents without writing custom tool code for each endpoint.

Step 1: Install the Adapter

npm install @frontmcp/adapters

Step 2: Add Adapter to Your App

Create or update your app to include the OpenAPI adapter:
import { App } from '@frontmcp/sdk';
import { OpenapiAdapter } from '@frontmcp/adapters';

@App({
  id: 'expense',
  name: 'Expense MCP App',
  adapters: [
    OpenapiAdapter.init({
      name: 'expense-api',
      baseUrl: 'https://api.example.com',
      url: 'https://api.example.com/openapi.json',
    }),
  ],
})
export default class ExpenseApp {}

Step 3: Configure Your Server

Add your app to the FrontMCP server:
src/main.ts
import { FrontMcp, LogLevel } from '@frontmcp/sdk';
import ExpenseApp from './apps/expense.app';

@FrontMcp({
  info: { name: 'Expense Server', version: '1.0.0' },
  apps: [ExpenseApp],
  http: { port: 3000 },
  logging: { level: LogLevel.INFO },
})
export default class Server {}

Step 4: Run and Test

1

Start the server

npm run dev
The server will load the OpenAPI spec and generate tools for each operation.
2

Verify tools are loaded

Check the console output for messages like: [INFO] Generated 15 tools from expense-api [INFO] Server listening on http://localhost:3000
3

Test with Inspector

npm run inspect
Open the MCP Inspector and you’ll see all generated tools from your API spec!

Understanding Generated Tools

Each OpenAPI operation becomes a tool with:

Tool Naming

Tools are named using the pattern: {adapter-name}:{method}_{path} For example:
  • GET /usersexpense-api:get_users
  • POST /expensesexpense-api:post_expenses
  • GET /expenses/{id}expense-api:get_expenses_id
If your OpenAPI spec includes operationId, that will be used instead of the generated name.

Input Schema

The adapter automatically converts OpenAPI parameters to Zod schemas:
OpenAPI spec
paths:
  /expenses:
    post:
      parameters:
        - name: category
          in: query
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  type: number
                description:
                  type: string
Becomes a tool with this input:
{
  category: string,    // from query parameter
  amount: number,      // from request body
  description: string  // from request body
}

Advanced Configuration

For comprehensive documentation on all configuration options including inputTransforms, toolTransforms, descriptionMode, and the x-frontmcp OpenAPI extension, see the full OpenAPI Adapter documentation.

Filter Operations

Only include specific endpoints:
OpenapiAdapter.init({
  name: 'billing-api',
  baseUrl: 'https://api.example.com',
  url: 'https://api.example.com/openapi.json',
  generateOptions: {
    // Only include operations starting with /invoices or /customers
    filterFn: (op) => op.path.startsWith('/invoices') || op.path.startsWith('/customers'),
  },
});

User-Based Authentication

Use authenticated user context for API requests:
OpenapiAdapter.init({
  name: 'user-api',
  baseUrl: 'https://api.example.com',
  url: 'https://api.example.com/openapi.json',
  headersMapper: (authInfo, headers) => {
    // Add user's token to API requests
    if (authInfo.token) {
      headers.set('authorization', `Bearer ${authInfo.token}`);
    }
    return headers;
  },
});

Multi-Tenant Setup

Include tenant ID from user context:
OpenapiAdapter.init({
  name: 'tenant-api',
  baseUrl: 'https://api.example.com',
  url: 'https://api.example.com/openapi.json',
  headersMapper: (authInfo, headers) => {
    // Add tenant ID header
    if (authInfo.user?.tenantId) {
      headers.set('x-tenant-id', authInfo.user.tenantId);
    }
    return headers;
  },
  bodyMapper: (authInfo, body) => {
    // Add tenant ID to all request bodies
    return {
      ...body,
      tenantId: authInfo.user?.tenantId,
    };
  },
});

How It Works

1

Load Spec

The adapter fetches and parses the OpenAPI specification from the URL or uses the provided spec object
2

Generate Tools

Each operation in the spec becomes an MCP tool with: - Automatic input schema (path params, query params, headers, body) - Type-safe validation using Zod - Description from the operation summary
3

Register Tools

Generated tools are registered with your app and inherit: - App-level plugins (caching, logging, etc.) - App-level providers - App-level authentication
4

Execute Requests

When a tool is called:
  1. Input is validated against the schema
  2. Headers/body are mapped (if configured)
  3. HTTP request is made to the API
  4. Response is parsed and returned

Common Patterns

Add multiple adapters to one app:
@App({
  id: 'integrations',
  name: 'Third-Party Integrations',
  adapters: [
    OpenapiAdapter.init({
      name: 'github',
      baseUrl: 'https://api.github.com',
      url: 'https://api.github.com/openapi.json',
    }),
    OpenapiAdapter.init({
      name: 'slack',
      baseUrl: 'https://api.slack.com',
      url: 'https://api.slack.com/openapi.json',
    }),
  ],
})
Mix generated and hand-written tools:
import CustomTool from './tools/custom.tool';

@App({
  id: 'hybrid',
  name: 'Hybrid App',
  tools: [CustomTool], // Hand-written
  adapters: [
    OpenapiAdapter.init({...}), // Auto-generated
  ],
})
Generated tools inherit app plugins:
import { CachePlugin } from '@frontmcp/plugins';

@App({
  id: 'cached-api',
  name: 'Cached API',
  plugins: [
    CachePlugin.init({
      type: 'redis',
      defaultTTL: 300,
    }),
  ],
  adapters: [
    OpenapiAdapter.init({...}),
  ],
})
Now all generated tools can use caching!

Troubleshooting

Possible causes:
  • Invalid OpenAPI spec URL
  • Spec is OpenAPI 2.0 (only 3.x supported)
  • All operations filtered out by filterFn
Solutions:
  • Verify the URL is accessible
  • Convert OpenAPI 2.0 to 3.x using Swagger Editor
  • Check your filter configuration
Possible causes:
  • Missing or invalid API credentials
  • Headers not properly mapped
Solutions:
  • Verify additionalHeaders or headersMapper configuration
  • Check that authInfo.token contains the expected value
  • Test the API directly with curl/Postman first
Possible cause:
  • OpenAPI spec has complex parameter definitions
Solution:
  • Use inputSchemaMapper to transform the schema:
generateOptions: {
  inputSchemaMapper: (schema) => {
    // Remove or modify fields
    delete schema.properties.internalField;
    return schema;
  },
}

What’s Next?

Full OpenAPI Adapter Docs

Explore all configuration options and advanced features

Authentication Setup

Learn how to configure authentication for your APIs

Plugin System

Add caching, logging, and other features to generated tools

Demo App

See a complete example using the OpenAPI adapter

Complete Example

Here’s a full working example with authentication and caching:
import { FrontMcp, App, LogLevel } from '@frontmcp/sdk';
import { OpenapiAdapter } from '@frontmcp/adapters';
import { CachePlugin } from '@frontmcp/plugins';

@App({
  id: 'expense',
  name: 'Expense Management',
  plugins: [
    CachePlugin.init({
      type: 'redis',
      defaultTTL: 300, // 5 minutes
      config: {
        host: 'localhost',
        port: 6379,
      },
    }),
  ],
  adapters: [
    OpenapiAdapter.init({
      name: 'expense-api',
      baseUrl: process.env.API_BASE_URL!,
      url: process.env.OPENAPI_SPEC_URL!,
      headersMapper: (authInfo, headers) => {
        // Add user's JWT token
        if (authInfo.token) {
          headers.set('authorization', `Bearer ${authInfo.token}`);
        }
        // Add tenant ID
        if (authInfo.user?.tenantId) {
          headers.set('x-tenant-id', authInfo.user.tenantId);
        }
        return headers;
      },
      bodyMapper: (authInfo, body) => {
        // Add user context to mutations
        return {
          ...body,
          createdBy: authInfo.user?.id,
          tenantId: authInfo.user?.tenantId,
        };
      },
      generateOptions: {
        // Only expose expense-related endpoints
        filterFn: (op) => op.path.startsWith('/expenses'),
      },
    }),
  ],
})
class ExpenseApp {}

@FrontMcp({
  info: { name: 'Expense Server', version: '1.0.0' },
  apps: [ExpenseApp],
  http: { port: 3000 },
  logging: { level: LogLevel.INFO },
  auth: {
    type: 'remote',
    name: 'auth-provider',
    baseUrl: process.env.AUTH_BASE_URL!,
  },
})
export default class Server {}