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.

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.

Prerequisites

  • A FrontMCP project initialized (see Installation)
  • An OpenAPI 3.x specification (URL or local file)
  • Basic understanding of REST APIs

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

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/cache';

@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?


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/cache';

@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 {}