Skip to main content
The OpenAPI Adapter automatically converts OpenAPI 3.x specifications into fully-functional MCP tools. Each API operation becomes a callable tool with built-in validation, authentication, and type safety.

Why use it

  • Zero boilerplate — Turn REST APIs into MCP tools without writing glue code
  • Type-safe — Automatic Zod schema generation from OpenAPI specs
  • Multi-auth support — Built-in support for multiple authentication providers
  • Production-ready — Comprehensive security validation and error handling
  • Flexible — Filter operations, customize schemas, and inject custom logic

Installation

npm install @frontmcp/adapters

Quick start

import { App } from '@frontmcp/sdk';
import { OpenapiAdapter } from '@frontmcp/adapters';

@App({
  id: 'my-api',
  name: 'My API MCP Server',
  adapters: [
    OpenapiAdapter.init({
      name: 'backend:api',
      baseUrl: process.env.API_BASE_URL!,
      url: process.env.OPENAPI_SPEC_URL!,
    }),
  ],
})
export default class MyApiApp {}

Configuration

Required Options

name
string
required
Unique identifier for this adapter instance. Used to prefix tool names when multiple adapters are present.
baseUrl
string
required
Base URL for API requests (e.g., https://api.example.com/v1).
spec
OpenAPIV3.Document
In-memory OpenAPI specification object. Use either spec or url, not both.
url
string
URL or file path to the OpenAPI specification. Can be a local file path or remote URL. Use either spec or url, not both.

Optional Configuration

additionalHeaders
Record<string, string>
Static headers applied to every request. Useful for API keys or static authentication tokens.
headersMapper
(authInfo: AuthInfo, headers: Headers) => Headers
Function to dynamically set headers based on authenticated user context. Headers set here are hidden from MCP clients.
bodyMapper
(authInfo: AuthInfo, body: any) => any
Function to transform or augment the request body before sending. Useful for adding tenant IDs or user-specific data.
loadOptions
LoadOptions
Options for loading the OpenAPI specification (headers, timeout, etc.). See mcp-from-openapi for details.
generateOptions
GenerateOptions
Options for tool generation. See Advanced Features for details.

Authentication

The OpenAPI adapter provides multiple authentication strategies with different security risk levels. Choose the approach that best fits your use case.

Strategy 1: Static Headers (Medium Risk)

Best for: Server-to-server APIs with static credentials.
OpenapiAdapter.init({
  name: 'my-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  additionalHeaders: {
    'x-api-key': process.env.API_KEY!,
    authorization: `Bearer ${process.env.API_TOKEN}`,
  },
});
Store credentials in environment variables or secrets manager, never hardcode them.
Best for: Multi-provider authentication (GitHub, Slack, Google, etc.). This approach maps OpenAPI security scheme names to authentication extractors. Each security scheme can use a different auth provider from the authenticated user context.
OpenapiAdapter.init({
  name: 'multi-auth-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  authProviderMapper: {
    // Map security scheme 'GitHubAuth' to GitHub token from user context
    GitHubAuth: (authInfo) => authInfo.user?.githubToken,

    // Map security scheme 'SlackAuth' to Slack token from user context
    SlackAuth: (authInfo) => authInfo.user?.slackToken,

    // Map security scheme 'ApiKeyAuth' to API key from user context
    ApiKeyAuth: (authInfo) => authInfo.user?.apiKey,
  },
});
How it works:
  1. Extracts security scheme names from OpenAPI spec (e.g., GitHubAuth, SlackAuth)
  2. For each tool, looks up the required security scheme
  3. Calls the corresponding extractor function to get the token from authInfo
  4. Applies the token to the request
Security Risk: LOW — Authentication is resolved from authenticated user context, not exposed to MCP clients.

Strategy 3: Custom Security Resolver (Low Risk)

Best for: Complex authentication logic or custom security requirements.
OpenapiAdapter.init({
  name: 'custom-auth-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  securityResolver: (tool, authInfo) => {
    // Use GitHub token for GitHub API tools
    if (tool.name.startsWith('github_')) {
      return { jwt: authInfo.user?.githubToken };
    }

    // Use Google token for Google API tools
    if (tool.name.startsWith('google_')) {
      return { jwt: authInfo.user?.googleToken };
    }

    // Use API key for admin tools
    if (tool.name.startsWith('admin_')) {
      return { apiKey: authInfo.user?.adminApiKey };
    }

    // Default to main JWT token
    return { jwt: authInfo.token };
  },
});
Security Risk: LOW — Full control over authentication resolution from user context.

Strategy 4: Static Auth (Medium Risk)

Best for: Server-to-server APIs where credentials don’t change per user.
OpenapiAdapter.init({
  name: 'backend-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  staticAuth: {
    jwt: process.env.API_JWT_TOKEN,
    apiKey: process.env.API_KEY,
  },
});
Security Risk: MEDIUM — Store credentials securely in environment variables or secrets manager.

Strategy 5: Dynamic Headers & Body Mapping (Low Risk)

Best for: Adding user-specific data (tenant IDs, user IDs) to requests.
OpenapiAdapter.init({
  name: 'tenant-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  headersMapper: (authInfo, headers) => {
    // Add authorization header
    if (authInfo.token) {
      headers.set('authorization', `Bearer ${authInfo.token}`);
    }

    // Add tenant ID from user context
    if (authInfo.user?.tenantId) {
      headers.set('x-tenant-id', authInfo.user.tenantId);
    }

    return headers;
  },
  bodyMapper: (authInfo, body) => {
    // Add user ID to all request bodies
    return {
      ...body,
      createdBy: authInfo.user?.id,
      tenantId: authInfo.user?.tenantId,
    };
  },
});
Security Risk: LOW — User-specific data is injected server-side, hidden from MCP clients.

Default Behavior (Medium Risk)

If no authentication configuration is provided, the adapter uses authInfo.token for all Bearer auth schemes.
OpenapiAdapter.init({
  name: 'simple-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  // No auth config - will use authInfo.token by default
});
Security Risk: MEDIUM — Only works for single Bearer auth. For multiple auth providers, use authProviderMapper or securityResolver.

Advanced Features

Filtering Operations

Control which API operations become MCP tools.
OpenapiAdapter.init({
  name: 'billing-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  generateOptions: {
    filterFn: (op) => op.path.startsWith('/invoices') || op.path.startsWith('/customers'),
  },
});

Input Schema Transformation

Customize the input schema for generated tools.
OpenapiAdapter.init({
  name: 'my-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  generateOptions: {
    inputSchemaMapper: (schema) => {
      // Remove sensitive fields from the input schema
      if (schema.properties?.password) {
        delete schema.properties.password;
      }

      // Add custom fields
      schema.properties.customField = {
        type: 'string',
        description: 'Custom field added by mapper',
      };

      return schema;
    },
  },
});

Load Options

Configure how the OpenAPI spec is loaded.
OpenapiAdapter.init({
  name: 'my-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  loadOptions: {
    headers: {
      authorization: `Bearer ${process.env.SPEC_ACCESS_TOKEN}`,
    },
    timeout: 10000, // 10 seconds
  },
});

How It Works

Request Processing

  1. Path Parameters — Interpolated into URL template (e.g., /users/{id}/users/123)
  2. Query Parameters — Validated and appended to URL
  3. Headers — Merged from additionalHeaders, headersMapper, and security config
  4. Request Body — Validated and transformed by bodyMapper (for POST/PUT/PATCH)
  5. Authentication — Applied via selected strategy (auth provider mapper, security resolver, etc.)

Response Processing

  • JSON responses — Automatically parsed to objects
  • Text responses — Returned as plain text
  • Error responses — Thrown as errors with status code and message

Complete Examples

Multi-Provider OAuth Application

import { App } from '@frontmcp/sdk';
import { OpenapiAdapter } from '@frontmcp/adapters';

@App({
  id: 'multi-provider-app',
  name: 'Multi-Provider Integration',
  adapters: [
    // GitHub API
    OpenapiAdapter.init({
      name: 'github',
      url: 'https://api.github.com/openapi.json',
      baseUrl: 'https://api.github.com',
      authProviderMapper: {
        GitHubAuth: (authInfo) => authInfo.user?.githubToken,
      },
    }),

    // Slack API
    OpenapiAdapter.init({
      name: 'slack',
      url: 'https://api.slack.com/openapi.json',
      baseUrl: 'https://api.slack.com',
      authProviderMapper: {
        SlackAuth: (authInfo) => authInfo.user?.slackToken,
      },
    }),
  ],
})
export default class MultiProviderApp {}

Multi-Tenant SaaS Application

import { App } from '@frontmcp/sdk';
import { OpenapiAdapter } from '@frontmcp/adapters';

@App({
  id: 'saas-app',
  name: 'SaaS Platform',
  adapters: [
    OpenapiAdapter.init({
      name: 'backend:api',
      spec: require('./openapi.json'),
      baseUrl: process.env.API_BASE_URL!,
      headersMapper: (authInfo, headers) => {
        // Add tenant ID to all requests
        if (authInfo.user?.tenantId) {
          headers.set('x-tenant-id', authInfo.user.tenantId);
        }

        // Add user authorization
        if (authInfo.token) {
          headers.set('authorization', `Bearer ${authInfo.token}`);
        }

        return headers;
      },
      bodyMapper: (authInfo, body) => {
        // Add user context to all mutations
        return {
          ...body,
          tenantId: authInfo.user?.tenantId,
          userId: authInfo.user?.id,
          timestamp: new Date().toISOString(),
        };
      },
    }),
  ],
})
export default class SaasApp {}

Expense Management (From Demo)

import { App } from '@frontmcp/sdk';
import { OpenapiAdapter } from '@frontmcp/adapters';

@App({
  id: 'expense',
  name: 'Expense MCP app',
  adapters: [
    OpenapiAdapter.init({
      name: 'backend:api',
      url: process.env.OPENAPI_SPEC_URL!,
      baseUrl: process.env.API_BASE_URL!,
      headersMapper: (authInfo, headers) => {
        const token = authInfo.token;
        if (token) {
          headers.set('authorization', `Bearer ${token}`);
        }
        return headers;
      },
    }),
  ],
})
export default class ExpenseMcpApp {}

Security Best Practices

Use Auth Provider Mapper

For multi-provider authentication, use authProviderMapper to map each security scheme to the correct auth provider. This provides LOW security risk.

Never Hardcode Credentials

Always store credentials in environment variables or secrets manager. Never commit credentials to source control.

Avoid includeSecurityInInput

Setting generateOptions.includeSecurityInInput: true exposes auth fields to MCP clients (HIGH risk). Only use for development/testing.

Validate User Context

Always validate that authInfo.user contains the expected fields before extracting tokens. Handle missing tokens gracefully.

Security Risk Levels

The adapter automatically validates your security configuration and assigns a risk score:
Risk LevelConfigurationDescription
LOWauthProviderMapper or securityResolverAuth resolved from user context, not exposed to clients
MEDIUM ⚠️staticAuth, additionalHeaders, or defaultStatic credentials or default behavior
HIGH 🚨generateOptions.includeSecurityInInput: trueAuth fields exposed to MCP clients (not recommended)

Troubleshooting

Cause: Your OpenAPI spec defines security schemes that aren’t mapped in authProviderMapper.Solution: Add all required security schemes to authProviderMapper:
authProviderMapper: {
  'GitHubAuth': (authInfo) => authInfo.user?.githubToken,
  'SlackAuth': (authInfo) => authInfo.user?.slackToken,
  // Add all security schemes from your spec
}
Cause: The tool requires authentication but no auth configuration was provided.Solution: Choose one of the authentication strategies:
  1. Add authProviderMapper (recommended for multi-provider)
  2. Add securityResolver (for custom logic)
  3. Add staticAuth (for server-to-server)
  4. Add additionalHeaders (for static API keys)
Cause: Tools may be filtered out by filterFn or excludeOperationIds.Solution: Check your filter configuration or remove filters to include all operations.
Cause: Authentication token is missing or invalid.Solution:
  1. Verify authInfo.user contains the expected token fields
  2. Check that token extraction returns a valid value
  3. Verify the token is not expired
  4. Check API logs for specific auth errors
Cause: OpenAPI spec may not be typed correctly.Solution: Cast the spec to OpenAPIV3.Document:
import { OpenAPIV3 } from 'openapi-types';

const spec = require('./openapi.json') as OpenAPIV3.Document;

API Reference

OpenapiAdapter.init(options)

Creates a new OpenAPI adapter instance. Parameters:
  • options: OpenApiAdapterOptions — Adapter configuration
Returns: Adapter instance ready for use in @App({ adapters: [...] })

Security Types

// Security context passed to mcp-from-openapi
interface SecurityContext {
  jwt?: string;
  apiKey?: string;
  basic?: { username: string; password: string };
  oauth2Token?: string;
  apiKeys?: Record<string, string>;
  customHeaders?: Record<string, string>;
}

// Auth info from FrontMCP
interface AuthInfo {
  token?: string;
  user?: {
    id?: string;
    email?: string;
    [key: string]: any; // Custom user fields
  };
}

Performance Tips

The adapter uses lazy loading — the OpenAPI spec is only loaded and tools are only generated on first use, not during initialization.
Combine with app-level plugins (caching, logging, metrics) to enhance all generated tools automatically.
Use filterFn to generate only the tools you need, reducing initialization time and memory usage.