> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenAPI Adapter

> Generate MCP tools directly from an OpenAPI spec and call them with strong validation.

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.

<Tip>
  Wrapping a large API (50+ operations)? The flat-exposure model the adapter uses hits the well-documented "tool overload" wall — Claude reliability degrades past \~20 tools, GPT Actions caps at 30. Consider the [Skilled OpenAPI plugin](/frontmcp/plugins/skilled-openapi/overview) instead, which curates operations into named **skills** behind three meta-tools and supports CI-signed bundle delivery. See the [coexistence guide](/frontmcp/plugins/skilled-openapi/coexistence) for running both in the same server.
</Tip>

## 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

```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
npm install @frontmcp/adapters
```

## Quick start

<CodeGroup>
  ```ts Basic usage theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  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 {}
  ```

  ```ts With authentication theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  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!,
        additionalHeaders: {
          'x-api-key': process.env.API_KEY!,
        },
      }),
    ],
  })
  export default class MyApiApp {}
  ```
</CodeGroup>

## Configuration

### Required Options

<ParamField path="name" type="string" required>
  Unique identifier for this adapter instance. Used to prefix tool names when multiple adapters are present.
</ParamField>

<ParamField path="baseUrl" type="string" required>
  Base URL for API requests (e.g., `https://api.example.com/v1`).
</ParamField>

<ParamField path="spec" type="OpenAPIV3.Document | OpenAPIV3_1.Document | object">
  In-memory OpenAPI specification object. Accepts typed documents or plain objects from JSON imports. Use either `spec` or `url`, not both.
</ParamField>

<ParamField path="url" type="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.
</ParamField>

### Optional Configuration

<ParamField path="additionalHeaders" type="Record<string, string>">
  Static headers applied to every request. Useful for API keys or static authentication tokens.
</ParamField>

<ParamField path="headersMapper" type="(ctx: FrontMcpContext, headers: Headers) => Headers">
  Function to dynamically set headers based on request context. Access `ctx.authInfo`, `ctx.sessionId`, `ctx.traceContext`, etc. Headers set here are hidden from MCP clients.
</ParamField>

<ParamField path="bodyMapper" type="(ctx: FrontMcpContext, body: any) => any">
  Function to transform or augment the request body before sending. Access `ctx.authInfo`, `ctx.sessionId`, etc. Useful for adding tenant IDs or user-specific data.
</ParamField>

<ParamField path="loadOptions" type="LoadOptions">
  Options for loading the OpenAPI specification — headers, timeout, validation, redirect-following, and SSRF/`$ref` resolution security. See [Spec Loading & \$ref Resolution Security](#spec-loading--ref-resolution-security-ssrf) for the secure defaults and how to enable external refs or internal targets.
</ParamField>

<ParamField path="generateOptions" type="GenerateOptions">
  Options for tool generation — filtering, naming, schema depth, format resolution, and more. See [Advanced Features](#advanced-features) and [Format Resolution](#format-resolution) for details.
</ParamField>

<ParamField path="inputTransforms" type="InputTransformOptions">
  Hide inputs from the schema and inject values at request time. Supports global, per-tool, and generator-based transforms. See [Input Schema Transforms](#input-schema-transforms).
</ParamField>

<ParamField path="toolTransforms" type="ToolTransformOptions">
  Customize generated tools with annotations, tags, descriptions, and more. Supports global, per-tool, and generator-based transforms. See [Tool Transforms](#tool-transforms).
</ParamField>

<ParamField path="schemaTransforms" type="SchemaTransformOptions">
  Modify input/output schema definitions at fetch() time. Use `schemaTransforms.input` and `schemaTransforms.output` for global, per-tool, or generator-based transforms.
</ParamField>

<ParamField path="outputSchema" type="OutputSchemaOptions">
  Control where and how output schema is exposed: `mode` (`'definition' | 'description' | 'both'`), `descriptionFormat` (`'jsonSchema' | 'summary'`), and a custom `descriptionFormatter`.
</ParamField>

<ParamField path="dataTransforms" type="DataTransformOptions">
  Transform tool definitions and API response data. Supports `preToolTransforms` (schema-level) and `postToolTransforms` (response-level), each with global, per-tool, and generator forms. See [Data Transforms](#data-transforms).
</ParamField>

<ParamField path="polling" type="SpecPollerOptions & { enabled: boolean }">
  Enable live polling to auto-detect spec changes and rebuild tools at runtime. Requires `url` option (not `spec`). See [Polling & Live Updates](/frontmcp/adapters/openapi-polling) for full configuration reference.
</ParamField>

<ParamField path="descriptionMode" type="'summaryOnly' | 'descriptionOnly' | 'combined' | 'full'">
  How to generate tool descriptions from OpenAPI operations. Default: `'summaryOnly'`.
</ParamField>

<ParamField path="logger" type="FrontMcpLogger">
  Logger instance for adapter diagnostics. When using `OpenapiAdapter.init()` within a FrontMCP app, the SDK automatically provides the logger via `setLogger()`. For standalone usage, you can optionally provide a logger implementing the `FrontMcpLogger` interface; if omitted, a console-based logger is created automatically.
</ParamField>

## 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.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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}`,
  },
});
```

<Warning>Store credentials in environment variables or secrets manager, never hardcode them.</Warning>

### Strategy 2: Auth Provider Mapper (Low Risk) ⭐ Recommended

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.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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: (ctx) => ctx.authInfo.user?.githubToken,

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

    // Map security scheme 'ApiKeyAuth' to API key from user context
    ApiKeyAuth: (ctx) => ctx.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 `ctx.authInfo`
4. Applies the token to the request

If an extractor returns `undefined`, nothing is sent for that scheme: the caller's own MCP token is not used in its place (unless `passthroughCallerToken: true`), and an operation that requires the scheme fails.

<Check>
  **Security Risk: LOW** — Authentication is resolved from the request context, not exposed to MCP clients.
</Check>

<Info>
  The `ctx` parameter in `authProviderMapper`, `headersMapper`, `bodyMapper`, and `securityResolver`
  callbacks is the `FrontMcpContext` containing `authInfo`, `sessionId`, `traceContext`, and more. By the time your tool executes,
  authentication has been verified and auth fields are populated.
</Info>

### Strategy 3: Custom Security Resolver (Low Risk)

Best for: Complex authentication logic or custom security requirements.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
OpenapiAdapter.init({
  name: 'custom-auth-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  securityResolver: (tool, ctx) => {
    const authInfo = ctx.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 };
    }

    // No credential for other tools. Don't return `authInfo.token`: it was issued for this MCP
    // server, not for the API (see "Default Behavior" below).
    return {};
  },
});
```

<Check>**Security Risk: LOW** — Full control over authentication resolution from request context.</Check>

### Strategy 4: Static Auth (Medium Risk)

Best for: Server-to-server APIs where credentials don't change per user.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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,
  },
});
```

<Warning>**Security Risk: MEDIUM** — Store credentials securely in environment variables or secrets manager.</Warning>

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

Best for: Adding user-specific data (tenant IDs, user IDs) to requests.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
OpenapiAdapter.init({
  name: 'tenant-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  headersMapper: (ctx, headers) => {
    const authInfo = ctx.authInfo;

    // Add tenant ID from user context (credentials for the API belong in
    // authProviderMapper / securityResolver, never the caller's own authInfo.token)
    if (authInfo.user?.tenantId) {
      headers.set('x-tenant-id', authInfo.user.tenantId);
    }

    // Add trace ID for distributed tracing
    headers.set('x-trace-id', ctx.traceContext.traceId);

    return headers;
  },
  bodyMapper: (ctx, body) => {
    const authInfo = ctx.authInfo;

    // Add user ID to all request bodies
    return {
      ...body,
      createdBy: authInfo.user?.id,
      tenantId: authInfo.user?.tenantId,
    };
  },
});
```

<Check>**Security Risk: LOW** — User-specific data is injected server-side, hidden from MCP clients.</Check>

### Default Behavior: No Credentials

If no authentication configuration is provided, the adapter sends no credentials, and operations that require authentication fail with `Authentication required for tool '...'`. The MCP client's own token (`ctx.authInfo.token`) is never forwarded implicitly: it was issued for your MCP server, and passing it on to another API is token passthrough, which the MCP specification forbids.

If the API is meant to accept the same token (same issuer and audience), opt in explicitly:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
OpenapiAdapter.init({
  name: 'simple-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  // Sends Authorization: Bearer <the MCP client's token> when nothing else supplied a credential
  passthroughCallerToken: true,
});
```

<Warning>
  **Security Risk: HIGH** — The API receives the token the client presented to your MCP server. Prefer
  `authProviderMapper`, `securityResolver` or `staticAuth` with credentials issued for the API.
</Warning>

## Advanced Features

### Filtering Operations

Control which API operations become MCP tools.

<CodeGroup>
  ```ts Filter by path theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  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'),
    },
  });
  ```

  ```ts Exclude specific operations theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    generateOptions: {
      excludeOperations: ['deprecatedEndpoint', 'internalOnly'],
    },
  });
  ```

  ```ts Include only specific operations theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    generateOptions: {
      includeOperations: ['getUser', 'createUser', 'updateUser'],
    },
  });
  ```
</CodeGroup>

### Input Schema Transformation

Customize the input schema definition for generated tools using `schemaTransforms.input`:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
OpenapiAdapter.init({
  name: 'my-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  schemaTransforms: {
    input: {
      global: (schema) => {
        // Remove sensitive fields from the input schema
        if (schema.properties?.password) {
          delete schema.properties.password;
        }

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

        return schema;
      },
    },
  },
});
```

### Input Schema Transforms

Hide inputs from AI/users and inject values server-side at request time. This is more powerful than `schemaTransforms.input` because it provides access to the authentication context.

<CodeGroup>
  ```ts Global transforms theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'tenant-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    inputTransforms: {
      global: [
        // Hide tenant header from AI, inject from user context
        // The injector receives { ctx, env, tool } — destructure ctx for FrontMcpContext
        { inputKey: 'X-Tenant-Id', inject: ({ ctx }) => ctx.authInfo.user?.tenantId },
        // Add correlation ID to all requests
        { inputKey: 'X-Correlation-Id', inject: () => crypto.randomUUID() },
      ],
    },
  });
  ```

  ```ts Per-tool transforms theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'audit-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    inputTransforms: {
      perTool: {
        'createAuditLog': [
          { inputKey: 'userId', inject: ({ ctx }) => ctx.authInfo.user?.id },
          { inputKey: 'timestamp', inject: () => new Date().toISOString() },
        ],
      },
    },
  });
  ```

  ```ts Dynamic generator theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    inputTransforms: {
      generator: (tool) => {
        // Add request ID to all mutating operations
        if (['post', 'put', 'patch', 'delete'].includes(tool.metadata.method)) {
          return [{ inputKey: 'X-Request-Id', inject: () => crypto.randomUUID() }];
        }
        return [];
      },
    },
  });
  ```
</CodeGroup>

<Check>
  **Security Benefit:** Sensitive inputs like tenant IDs and user IDs are injected server-side, never exposed to MCP clients.
</Check>

### Tool Transforms

Customize generated tools with annotations, tags, descriptions, and more.

<CodeGroup>
  ```ts Global transforms theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    toolTransforms: {
      global: {
        annotations: { openWorldHint: true },
      },
    },
  });
  ```

  ```ts Per-tool transforms theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    toolTransforms: {
      perTool: {
        'createUser': {
          annotations: { destructiveHint: false },
          tags: ['user-management'],
        },
        'deleteUser': {
          annotations: { destructiveHint: true },
          tags: ['user-management', 'dangerous'],
        },
      },
    },
  });
  ```

  ```ts Dynamic generator theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    toolTransforms: {
      generator: (tool) => {
        // Auto-annotate based on HTTP method
        if (tool.metadata.method === 'get') {
          return { annotations: { readOnlyHint: true, destructiveHint: false } };
        }
        if (tool.metadata.method === 'delete') {
          return { annotations: { destructiveHint: true } };
        }
        return undefined;
      },
    },
  });
  ```
</CodeGroup>

**Available transform properties:**

| Property | Type | Description |
| - | - | - |
| `name` | `string \| function` | Override or transform the tool name |
| `description` | `string \| function` | Override or transform the tool description |
| `annotations` | `ToolAnnotations` | MCP tool behavior hints |
| `tags` | `string[]` | Categorization tags |
| `examples` | `ToolExample[]` | Usage examples |
| `hideFromDiscovery` | `boolean` | Hide tool from listing |
| `ui` | `ToolUIConfig` | UI configuration for tool forms |

### Output Schema Display

Control how output schemas appear in tool definitions via the `outputSchema` option:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
OpenapiAdapter.init({
  name: 'my-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  outputSchema: {
    mode: 'description',           // 'definition' | 'description' | 'both'
    descriptionFormat: 'summary',  // 'jsonSchema' | 'summary'
  },
});
```

| Field | Type | Description |
| - | - | - |
| `mode` | `'definition' \| 'description' \| 'both'` | Where to expose the schema. Default: `'definition'`. |
| `descriptionFormat` | `'jsonSchema' \| 'summary'` | Format used when schema appears in description. Default: `'summary'`. |
| `descriptionFormatter` | `(schema, ctx) => string \| Promise<string>` | Custom formatter (sync or async, e.g., LLM-generated). |

#### Custom Schema Formatter

Provide your own formatter (can be async to support LLM-based generation):

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
OpenapiAdapter.init({
  name: 'my-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  outputSchema: {
    mode: 'description',
    descriptionFormatter: async (schema, ctx) => {
      // ctx.tool, ctx.adapterOptions, ctx.originalDescription available
      return `Response: ${JSON.stringify(schema.properties || {})}`;
    },
  },
});
```

### Data Transforms

Transform tool definitions and API response data via `dataTransforms`:

#### Pre-Tool Transforms (Schema Level)

Modify schemas and descriptions before tools are wrapped — applied during `fetch()` to `McpOpenAPITool` definitions:

<CodeGroup>
  ```ts Global transform theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    dataTransforms: {
      preToolTransforms: {
        global: {
          transformSchema: (outputSchema, ctx) => undefined, // remove from all
          transformDescription: (desc, outputSchema, ctx) =>
            outputSchema ? `${desc}\n\nReturns: ${JSON.stringify(outputSchema)}` : desc,
        },
      },
    },
  });
  ```

  ```ts Per-tool transforms theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    dataTransforms: {
      preToolTransforms: {
        perTool: {
          listUsers: {
            transformDescription: (desc) => `${desc}\n\nPaginated list of users.`,
          },
          getUser: {
            transformSchema: (schema) => schema && { ...schema, description: 'User object with profile data' },
          },
        },
      },
    },
  });
  ```

  ```ts Dynamic generator theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    dataTransforms: {
      preToolTransforms: {
        generator: (tool) => {
          if (tool.metadata.method === 'get') {
            return {
              transformSchema: () => undefined,
              transformDescription: (desc, schema) =>
                schema ? `${desc}\n\n---\nOutput: ${JSON.stringify(schema)}` : desc,
            };
          }
          return undefined;
        },
      },
    },
  });
  ```
</CodeGroup>

#### Post-Tool Transforms (Response Level)

Transform API response data at runtime:

<CodeGroup>
  ```ts Global transform theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    dataTransforms: {
      postToolTransforms: {
        global: {
          transform: (data) => ({ ...(data as object), _transformedAt: new Date().toISOString() }),
        },
      },
    },
  });
  ```

  ```ts Per-tool transforms theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    dataTransforms: {
      postToolTransforms: {
        perTool: {
          listUsers: {
            transform: (data) => (data as any)?.users ?? data,
          },
          getUser: {
            transform: (data) => ({
              ...(data as any),
              fullName: `${(data as any)?.firstName} ${(data as any)?.lastName}`,
            }),
          },
        },
      },
    },
  });
  ```

  ```ts With filter theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    dataTransforms: {
      postToolTransforms: {
        global: {
          filter: (ctx) => ctx.ok && ctx.status === 200,
          transform: (data) => ({ result: data }),
        },
      },
    },
  });
  ```
</CodeGroup>

**Post-tool transform context:**

| Property | Type | Description |
| - | - | - |
| `ctx` | `FrontMcpContext` | Full request context |
| `tool` | `McpOpenAPITool` | Tool definition |
| `status` | `number` | HTTP response status code |
| `ok` | `boolean` | Whether response was successful |
| `adapterOptions` | `object` | Adapter configuration |

<Check>
  **Error Handling:** Transform failures are logged and the original data is returned, ensuring graceful degradation.
</Check>

### x-frontmcp OpenAPI Extension

Configure tool behavior directly in your OpenAPI spec using the `x-frontmcp` extension:

```yaml openapi.yaml theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
paths:
  /users:
    get:
      operationId: listUsers
      summary: List all users
      x-frontmcp:
        annotations:
          readOnlyHint: true
          idempotentHint: true
        cache:
          ttl: 300
        tags:
          - users
          - public-api
    delete:
      operationId: deleteUser
      summary: Delete a user
      x-frontmcp:
        annotations:
          destructiveHint: true
        tags:
          - users
          - dangerous
```

**Extension properties:**

| Property | Type | Description |
| - | - | - |
| `annotations` | `object` | Tool behavior hints (readOnlyHint, destructiveHint, idempotentHint, openWorldHint, title) |
| `cache` | `object` | Cache config: `ttl` (seconds), `slideWindow` (boolean) |
| `codecall` | `object` | CodeCall config: `enabledInCodeCall`, `visibleInListTools` |
| `tags` | `string[]` | Categorization tags |
| `hideFromDiscovery` | `boolean` | Hide from tool listing |
| `examples` | `array` | Usage examples with input/output |

<Tip>
  Use `x-frontmcp` in your OpenAPI spec for declarative configuration.
  Use `toolTransforms` in adapter config to override spec values.
</Tip>

### Description Mode

Control how tool descriptions are generated from OpenAPI operations:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
OpenapiAdapter.init({
  name: 'my-api',
  url: 'https://api.example.com/openapi.json',
  baseUrl: 'https://api.example.com',
  descriptionMode: 'combined', // Default: 'summaryOnly'
});
```

| Mode | Description |
| - | - |
| `'summaryOnly'` | Use only the OpenAPI summary (default) |
| `'descriptionOnly'` | Use only the OpenAPI description |
| `'combined'` | Summary followed by description |
| `'full'` | Summary, description, and operation details |

### Format Resolution

Enrich generated tool schemas with concrete constraints derived from OpenAPI `format` values. When enabled, formats like `uuid`, `date-time`, `email`, `int32`, etc. are expanded into patterns, descriptions, and min/max constraints.

<CodeGroup>
  ```ts Enable Built-in Resolvers theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    generateOptions: {
      resolveFormats: true,
    },
  });
  // A field with format: "uuid" gets pattern: "^[0-9a-f]{8}-..." added
  // A field with format: "int32" gets minimum/maximum constraints
  // A field with format: "email" gets a pattern and description
  ```

  ```ts Custom Format Resolvers theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    generateOptions: {
      resolveFormats: true, // Include built-in resolvers
      formatResolvers: {
        // Add or override format resolvers
        'phone': (schema) => ({
          ...schema,
          pattern: '^\\+[1-9]\\d{1,14}$',
          description: 'E.164 phone number',
        }),
        'currency': (schema) => ({
          ...schema,
          pattern: '^[A-Z]{3}$',
          description: 'ISO 4217 currency code',
        }),
      },
    },
  });
  ```

  ```ts Custom Resolvers Only theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    baseUrl: 'https://api.example.com',
    generateOptions: {
      // Without resolveFormats, only custom resolvers are applied
      formatResolvers: {
        'phone': (schema) => ({
          ...schema,
          pattern: '^\\+[1-9]\\d{1,14}$',
        }),
      },
    },
  });
  ```
</CodeGroup>

| Option | Type | Default | Description |
| - | - | - | - |
| `resolveFormats` | `boolean` | `false` | Enable built-in format resolvers (uuid, date-time, email, int32, etc.) |
| `formatResolvers` | `Record<string, FormatResolver>` | `undefined` | Custom format resolvers. When used with `resolveFormats: true`, custom resolvers are merged with built-ins (custom takes precedence). |

<Tip>
  Format resolution improves tool input validation by giving AI models concrete constraints. For example, a `uuid` field gets a regex pattern, helping the model generate valid values.
</Tip>

### Load Options

Configure how the OpenAPI spec is loaded.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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
  },
});
```

### Spec Loading & \$ref Resolution Security (SSRF)

<Warning>
  **Security Advisories (GHSA-v6ph-xcq9-qxxj, GHSA-65h7-9wrw-629c):** Loading an OpenAPI spec fetches attacker-influenceable URLs — both the spec `url` and any external `$ref` pointers — which is an SSRF vector (cloud-metadata theft, internal-network scanning, local file reads). Hostname-string denylists are bypassable via DNS names that resolve to internal IPs (e.g. `http://127.0.0.1.nip.io/`), HTTP redirects, and IPv4-mapped IPv6 forms. **Use `mcp-from-openapi` ≥ 2.5.0**, which resolves DNS and validates the *resolved IP* (not just the hostname string), guards the initial spec-URL fetch (not only `$ref`s), and re-validates every redirect hop.
</Warning>

FrontMCP applies **secure defaults** on top of that guard:

* **External `$ref` resolution is disabled by default.** Only internal `#/...` JSON-pointer refs are resolved; inline `spec:` objects are unaffected. To resolve external refs, set `loadOptions.refResolution` explicitly (you then own its allow/deny lists).
* **Spec-URL redirects are not followed by default.** A 3xx from your spec host won't be chased toward a (possibly internal) target. Opt in with `loadOptions.followRedirects: true` (each hop is still re-validated on `mcp-from-openapi` ≥ 2.5.0).
* **Internal/private targets are blocked** for the spec `url` and external `$ref`s alike — loopback (`127.0.0.0/8`, `::1`), RFC 1918 private (`10/8`, `172.16/12`, `192.168/16`), CGNAT (`100.64/10`), link-local / cloud metadata (`169.254/16`, `fe80::/10`), unspecified, multicast, and ULA (`fc00::/7`) — and hostnames are **DNS-resolved** and re-checked, closing the `127.0.0.1.nip.io` class of bypass. Blocked names include `localhost` and `metadata.google.internal`.
* **`file://` is blocked**, preventing local file reads (e.g. `$ref: "file:///etc/passwd"`).

`loadOptions.refResolution` configures both surfaces — `allowedHosts` / `blockedHosts` / `allowInternalIPs` apply to the spec URL **and** external `$ref`s:

<CodeGroup>
  ```ts Default (Secure) theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  // Default behavior — no configuration needed.
  // External $refs are NOT resolved; spec-URL redirects are NOT followed;
  // the spec URL is fetched only if it (and its resolved IP) is public.
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
  });
  ```

  ```ts Enable External Refs (public hosts only) theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  // Opt back into external $ref resolution. Internal/private targets stay
  // blocked; hosts are DNS-resolved and redirects re-validated.
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    loadOptions: {
      refResolution: {
        allowedProtocols: ['http', 'https'],
      },
    },
  });
  ```

  ```ts Local / Internal Development theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  // Spec URL on localhost or a private network (e.g. http://localhost:3000).
  // allowInternalIPs also re-allows the spec-URL fetch to internal targets.
  OpenapiAdapter.init({
    name: 'local-api',
    url: 'http://localhost:3000/openapi.json',
    loadOptions: {
      refResolution: { allowInternalIPs: true },
    },
  });
  ```

  ```ts Restrict to Specific Hosts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  // Only allow $refs pointing to your own schema servers
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    loadOptions: {
      refResolution: {
        allowedHosts: ['schemas.example.com', 'api.example.com'],
      },
    },
  });
  ```

  ```ts Allow file:// Protocol theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  // Enable file:// for specs that reference local schema files
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    loadOptions: {
      refResolution: {
        allowedProtocols: ['http', 'https', 'file'],
      },
    },
  });
  ```

  ```ts Allow Internal IPs theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  // WARNING: Only use in trusted environments (e.g., internal APIs)
  OpenapiAdapter.init({
    name: 'internal-api',
    url: 'http://10.0.0.5:8080/openapi.json',
    loadOptions: {
      refResolution: {
        allowInternalIPs: true,
      },
    },
  });
  ```

  ```ts Block All External Refs (FrontMCP default) theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  // This is the default — only local #/ JSON pointer refs resolve. Shown here for
  // clarity; you don't need to set it unless overriding a broader policy.
  OpenapiAdapter.init({
    name: 'my-api',
    url: 'https://api.example.com/openapi.json',
    loadOptions: {
      refResolution: {
        allowedProtocols: [], // No external protocols
      },
    },
  });
  ```
</CodeGroup>

<Note>
  **`followRedirects`** (a `loadOptions` field, not part of `refResolution`) defaults to `false` in FrontMCP — the spec-URL fetch will not follow 3xx redirects unless you opt in. On `mcp-from-openapi` ≥ 2.5.0 each followed hop is re-validated against the SSRF guard.
</Note>

#### `refResolution` Options Reference

These apply to **both** the spec-URL fetch and external `$ref` resolution (`mcp-from-openapi` ≥ 2.5.0).

| Option | Type | Default (FrontMCP) | Description |
| - | - | - | - |
| `allowedProtocols` | `string[]` | `[]` | Protocols allowed for external `$ref` resolution (http, https, file, …). **FrontMCP defaults this to `[]`** (external refs disabled); set `['http','https']` to enable. |
| `allowedHosts` | `string[]` | `undefined` | When set, only the spec URL / `$ref` URLs pointing to these hostnames are allowed. All other hosts are blocked. |
| `blockedHosts` | `string[]` | `undefined` | Additional hostnames/IPs to block, on top of the built-in internal-address block list. |
| `allowInternalIPs` | `boolean` | `false` | Set to `true` to allow loopback / private / internal targets for the spec URL **and** `$ref`s (skips the internal-address block list and DNS re-check). **Warning:** re-exposes SSRF; use only in trusted/local environments. |

## Polling & Live Updates

The adapter can automatically detect changes to your OpenAPI spec and rebuild tools at runtime — no server restart required.

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
OpenapiAdapter.init({
  name: 'my-api',
  baseUrl: 'https://api.example.com',
  url: 'https://api.example.com/openapi.json',
  polling: {
    enabled: true,
    intervalMs: 60000, // Poll every minute
  },
});
```

When a spec change is detected (via content hash, ETag, or auto strategy), the adapter:

1. Resets its internal generator
2. Calls `fetch()` to regenerate all tools
3. Notifies subscribers via `onUpdate()`

<Note>
  Polling re-fetches the same spec `url` on a timer through the **same SSRF guard** as the initial load (GHSA-65h7-9wrw-629c) — it inherits your `loadOptions.refResolution` policy, so internal/loopback spec servers stay blocked unless you set `refResolution.allowInternalIPs: true`. See [Spec Loading & \$ref Resolution Security](#spec-loading--ref-resolution-security-ssrf).
</Note>

Subscribe to updates for logging, metrics, or downstream notifications:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
const adapter = OpenapiAdapter.init({
  name: 'my-api',
  baseUrl: 'https://api.example.com',
  url: 'https://api.example.com/openapi.json',
  polling: { enabled: true, intervalMs: 60000 },
});

adapter.onUpdate((response) => {
  console.log(`Tools rebuilt: ${response.tools?.length} tools`);
});
adapter.startPolling();
```

<Info>
  For complete configuration options (retry, health monitoring, change detection strategies, and best practices), see the [Polling & Live Updates](/frontmcp/adapters/openapi-polling) guide.
</Info>

## 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

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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: (ctx) => ctx.authInfo.user?.githubToken,
      },
    }),

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

### Multi-Tenant SaaS Application

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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: (ctx, headers) => {
        // Add tenant ID to all requests
        if (ctx.authInfo?.user?.tenantId) {
          headers.set('x-tenant-id', ctx.authInfo.user.tenantId);
        }

        // Credentials for the API come from authProviderMapper / securityResolver / staticAuth,
        // never from the caller's own ctx.authInfo.token
        return headers;
      },
      bodyMapper: (ctx, body) => {
        // Add user context to all mutations
        return {
          ...body,
          tenantId: ctx.authInfo?.user?.tenantId,
          userId: ctx.authInfo?.user?.id,
          timestamp: new Date().toISOString(),
        };
      },
    }),
  ],
})
export default class SaasApp {}
```

### Expense Management (From Demo)

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
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!,
      // The expense backend accepts tokens issued for this MCP server (same issuer and audience),
      // so the caller's token is forwarded explicitly. Security risk: HIGH.
      passthroughCallerToken: true,
    }),
  ],
})
export default class ExpenseMcpApp {}
```

## Security Best Practices

<CardGroup cols={2}>
  <Card title="Use Auth Provider Mapper" icon="shield-check">
    For multi-provider authentication, use `authProviderMapper` to map each security scheme to the correct auth provider. This provides **LOW security risk**.
  </Card>

  <Card title="Never Hardcode Credentials" icon="triangle-exclamation">
    Always store credentials in environment variables or secrets manager. Never commit credentials to source control.
  </Card>

  <Card title="Avoid includeSecurityInInput" icon="xmark">
    Setting `generateOptions.includeSecurityInInput: true` exposes auth fields to MCP clients (**HIGH risk**). Only use
    for development/testing.
  </Card>

  <Card title="Validate User Context" icon="user-check">
    Always validate that `authInfo.user` contains the expected fields before extracting tokens. Handle missing tokens gracefully.
  </Card>
</CardGroup>

## Security Risk Levels

The adapter automatically validates your security configuration and assigns a risk score:

| Risk Level | Configuration | Description |
| - | - | - |
| **LOW** ✅ | `authProviderMapper` or `securityResolver` | Auth resolved from user context, not exposed to clients |
| **MEDIUM** ⚠️ | `staticAuth`, `additionalHeaders`, or default | Static credentials, or no credentials at all |
| **HIGH** 🚨 | `generateOptions.includeSecurityInInput` (`true` or a list of schemes), or `securitySchemesInInput` | Auth fields exposed to MCP clients (not recommended) |
| **HIGH** 🚨 | `passthroughCallerToken: true` | The MCP client's own token is sent to the API |

`passthroughCallerToken: true` scores HIGH whenever the caller's token can reach the API: on its own, and alongside an `authProviderMapper` (the token is sent when no mapper function returns a credential). A `securityResolver` or a non-empty `staticAuth` leaves it unused: `staticAuth` answers for every credential the mapper returns nothing for.

Credential sources are tried in this order: `securityResolver`, then `authProviderMapper`, then `staticAuth` (which fills every credential no mapper function returned; a mapped value wins), then `passthroughCallerToken`. A security scheme with no `authProviderMapper` entry is refused at startup unless `staticAuth` covers it, `additionalHeaders` carries its credential, `headersMapper` may set it (a header or cookie scheme; checked on each request, and logged at startup), or `passthroughCallerToken` does for an HTTP bearer scheme; with `passthroughCallerToken` it gets the caller's token, and a `SECURITY WARNING` names it. The caller's token is only ever sent as a bearer token, so it never covers an API key, basic, OAuth2 or OpenID Connect scheme. An operation that requires authentication is sent only when the request carries a credential for one of its own schemes (in that scheme's header, query parameter or cookie), whatever supplied it: a credential option, the tool input (`securitySchemesInInput`, or `includeSecurityInInput`: `true` for every scheme, a list such as `['ReportsKey']` for the schemes it names, which is the same as listing them in `securitySchemesInInput`; the schemes a list leaves out still need a credential source), `additionalHeaders` or `headersMapper` (these two can set headers, including `Cookie`, but no query parameter); otherwise it fails with `Authentication required for tool '…'` before any request. A credential in the tool input is used for a scheme only when no other source supplies one: the model chooses it, so a server credential always wins.

## Built-in Security Protections

Beyond authentication, the adapter includes defense-in-depth protections:

| Protection | Description |
| - | - |
| **\$ref SSRF Prevention** | Blocks `file://` protocol and internal/private IPs during `$ref` dereferencing. Configurable via [`refResolution`](#ref-resolution-security). |
| **SSRF Prevention** | Validates server URLs, blocks dangerous protocols (`file://`, `javascript://`, `data:`) |
| **Header Injection** | Rejects control characters (`\r`, `\n`, `\x00`, `\f`, `\v`) in header values |
| **Prototype Pollution** | Blocks reserved JS keys (`__proto__`, `constructor`, `prototype`) in input transforms |
| **Request Size Limits** | Content-Length validation with integer overflow protection |
| **Query Param Collision** | Detects conflicts between security and user input parameters |

**Auth Type Routing:** Tokens are automatically routed to the correct context field based on security scheme type (Bearer → `jwt`, API Key → `apiKey`, Basic → `basic`, OAuth2 → `oauth2Token`).

<Tip>
  These protections are automatic—no configuration required. See the [README](https://github.com/agentfront/frontmcp/tree/main/libs/adapters/src/openapi#security-protections) for implementation details.
</Tip>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Error: Missing auth provider mappings">
    **Cause:** Your OpenAPI spec defines security schemes that aren't mapped in `authProviderMapper`.

    **Solution:** Add all required security schemes to `authProviderMapper`:

    ```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    authProviderMapper: {
      'GitHubAuth': (ctx) => ctx.authInfo.user?.githubToken,
      'SlackAuth': (ctx) => ctx.authInfo.user?.slackToken,
      // Add all security schemes from your spec
    }
    ```
  </Accordion>

  <Accordion title="Error: Authentication required for tool '…'">
    **Cause:** The operation requires authentication, and no credential source gave the request a credential for any of its security schemes (for example, `passthroughCallerToken` alone for an API-key scheme).

    **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)
    5. Set `passthroughCallerToken: true`, only if the API accepts the token clients present to your MCP server (HTTP bearer schemes only)

    The adapter no longer sends the caller's own token when nothing is configured, or when an `authProviderMapper` function returns `undefined`.
  </Accordion>

  <Accordion title="Tools not appearing">
    **Cause:** Tools may be filtered out by `filterFn` or `excludeOperations`.

    **Solution:** Check your filter configuration or remove filters to include all operations.
  </Accordion>

  <Accordion title="Request fails with 401 Unauthorized">
    **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
  </Accordion>

  <Accordion title="TypeScript errors with OpenAPI spec">
    **Cause:** OpenAPI spec may not be typed correctly.

    **Solution:** Cast the spec to `OpenAPIV3.Document`:

    ```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    import { OpenAPIV3 } from 'openapi-types';

    const spec = require('./openapi.json') as OpenAPIV3.Document;
    ```
  </Accordion>
</AccordionGroup>

## 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

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
// 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

<Tip>
  The adapter uses lazy loading — the OpenAPI spec is only loaded and tools are only generated on first use, not during
  initialization.
</Tip>

<Tip>Combine with app-level plugins (caching, logging, metrics) to enhance all generated tools automatically.</Tip>

<Tip>Use `filterFn` to generate only the tools you need, reducing initialization time and memory usage.</Tip>

## Links & Resources

<CardGroup cols={2}>
  <Card title="Demo Application" icon="code" href="https://github.com/agentfront/frontmcp/tree/main/apps/demo/src/apps/expenses">
    See the expense management demo app using the OpenAPI adapter
  </Card>

  <Card title="Example OpenAPI Spec" icon="file-code" href="https://frontmcp-test.proxy.beeceptor.com/openapi.json">
    OpenAPI spec used in the demo application
  </Card>

  <Card title="Generator Library" icon="npm" href="https://www.npmjs.com/package/mcp-from-openapi">
    Underlying library for OpenAPI to MCP conversion
  </Card>

  <Card title="Source Code" icon="github" href="https://github.com/agentfront/frontmcp/tree/main/libs/adapters/src/openapi">
    View the adapter source code
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.