Agentic Data Plane

OpenAPI Managed MCP Server

The OpenAPI managed MCP server is the "bring your own API" escape hatch. Hand it an OpenAPI 3.0 spec, and it generates one MCP tool per operation in the spec. It needs no custom code and no per-API managed type, which makes it useful when the API you want to expose is not in the catalog.

After reading this page, you will be able to:

  • Configure the OpenAPI managed MCP server against an OpenAPI 3.0 spec

  • Pick the right authentication mode for the upstream API

  • Verify generated tools through the Inspector

What this MCP server does

The OpenAPI managed type:

  • Loads an OpenAPI 3.0 spec from a URL or pasted JSON/YAML. Swagger 2.0 specs are not supported.

  • Generates one MCP tool per operation, with input schemas derived from the spec’s parameter and request-body schemas. allOf, oneOf, and anyOf are flattened into a single object schema; discriminator is ignored and callbacks are not supported.

  • Forwards calls to the upstream API, applying the configured authentication.

  • Optionally presents the API’s read endpoints as SQL-queryable tables, through two more tools. SQL exposure is on by default for servers created in the UI. See Expose API data as SQL tables.

By default the generator walks schemas up to a depth of 5 (max_schema_depth); schemas nested deeper than that are truncated.

For a complete walkthrough against a real API, see Set Up Snowflake as an OpenAPI MCP Server.

Prerequisites

  • An HTTP API with an OpenAPI 3.0 spec.

  • The spec URL or the spec content itself.

  • Credentials for the API, if it requires them.

Configure

  1. Open MCP servers in the sidebar.

  2. Click Add MCP server.

  3. Click the OpenAPI card in the marketplace picker.

  4. Replace the suggested Name, and optionally add a Description.

  5. Optionally, turn off Enable code mode. It’s on by default in the create form for OpenAPI servers. rpk ai mcp-server create leaves it off unless you pass --code-mode. See Code Mode.

  6. In the OpenAPI configuration form:

    • Spec: A URL to the OpenAPI 3.0 spec (JSON or YAML).

    • Spec Content: The spec itself, pasted or loaded from a file, when you don’t host it. Set exactly one of Spec and Spec Content. Inline specs are capped at 3 MiB; host larger specs and use the URL instead.

    • Base URL (optional): Useful when the spec’s servers block doesn’t match your environment.

    • Filter (optional): Include or exclude operations by tag, operationId glob, path glob, or HTTP method.

    • Max Schema Depth (optional): Maximum depth for schema conversion. Defaults to 5.

    • Extract Headers (optional): Response header names to surface to the agent, such as X-CSRF-Token or Set-Cookie. Listed headers are appended to the tool result; all other response headers are dropped. Matching is case-insensitive.

  7. Configure authentication (see Authentication).

  8. Under Queryable:

  9. Click Create server.

Authentication

OpenAPI is the most flexible managed type for authentication, because the upstream API can need anything. All the standard authentication modes apply:

Mode Use when

No Auth

Public APIs (rare in practice).

Bearer

API expects a bearer token. The server sends Authorization: Bearer <token>.

API Key

API expects a key in a header or query parameter. Choose the placement and the parameter name.

Basic

API expects HTTP Basic authentication (a username and password).

User OAuth

API supports OAuth on behalf of users and you want per-user identities. Requires an OAuth Provider configured for that API. See Configure User-Delegated OAuth.

Service Account OAuth

API supports the OAuth client-credentials grant and you want a single shared service-account identity for every call, rather than per-user tokens.

For Service Account OAuth, the gateway runs the client-credentials exchange once per server and shares the resulting token across all callers. Supply the client ID, a secret-store reference for the client secret, the token URL, and any scopes the API requires. Store the client secret in the Agentic Data Plane secret store under an UPPER_SNAKE_CASE key, such as OAUTH_CLIENT_SECRET, and reference it from the form. Use an https token endpoint.

Test

  1. Open the Inspector tab.

  2. The Tools panel lists every operation from your spec. Each tool is named from the operation’s operationId, lowercased, with characters other than letters and digits replaced by underscores. For example, getPetById becomes getpetbyid. An operation without an operationId is named from its method and path, for example get_pets_by_id for GET /pets/{id}. Tool names longer than 64 characters are truncated and given a short hash suffix to keep them unique, because LLM providers cap tool-name length. Parameter names that start with $ (common in OData APIs, such as $top and $filter) appear without the $ prefix in the tool schema; the server still sends the original parameter name to the API.

  3. Click a tool and run it. The Inspector renders a form from the operation’s parameter and request-body schemas.

See Test an MCP Server’s Tools with the Inspector for general Inspector usage.

Use with agents

After tools generate cleanly, point an agent at the Server URL on the server’s Connection tab. The agent sees one tool per OpenAPI operation, named accordingly.

To attach the server to a managed agent, add it in the agent’s Tools section. See Tools.

Each tool returns the upstream response as a structured result with the fields status_code, headers, and body. body is the parsed JSON payload when the response is JSON, or the raw response text otherwise. headers holds the response headers you selected with Extract Headers, and is empty when none are configured. Agents that read structured output can address these fields directly, and a plain-text copy of the same result is also included for clients that read only text. A 4xx or 5xx response surfaces as a tool error rather than a structured result.

Every generated tool also accepts an optional extra_headers input: an array of objects, each with a key (the header name) and a value. It lets the agent set request headers the spec doesn’t declare, for example "extra_headers": [{"key": "X-CSRF-Token", "value": "abc"}]. Agent-supplied headers can’t override the configured authentication headers or the spec’s own header parameters. Combined with Extract Headers, this supports APIs that round-trip headers, such as a CSRF token fetched from one response and echoed on the next request.

Expose API data as SQL tables

Besides one tool per operation, the server can present the API’s read endpoints as SQL-queryable tables, so an agent can filter records the way it would query a database instead of calling an operation and sifting the response. The table catalog is derived from your spec, so there is nothing to author by hand.

SQL exposure is the Enabled toggle under Queryable in the server’s configuration. The create form turns it on, so a server you create there is queryable unless you turn it off. Creating a server with rpk ai or the API leaves SQL exposure off unless the managed configuration sets it, and a server that already exists keeps whatever it was saved with. SQL exposure adds two tools:

  • list_tables: Return the derived table catalog. For each table, the catalog lists its columns and their types, which columns can be filtered and with which operators, whether each filter is exact, any filters the table requires, the page-size limit, and a schema fingerprint that every query response repeats so a caller can tell when the catalog changed.

  • execute_query: Run one query against a table and return a page of rows plus a cursor for the next page.

Both tools are read-only. Run list_tables after you create the server: the catalog is the only reliable account of what your spec produced.

With user-delegated OAuth, a caller who hasn’t connected an account yet can still read the catalog and plan a query, because the catalog comes from the stored spec and carries no API data. Every call that reaches the API, execute_query included, still needs the connection.

What becomes a table

Each GET operation whose success response is a JSON array of objects becomes one table, as does an operation whose response object wraps a single array of rows, with sibling fields such as a total count alongside it. The table takes its name from the operation’s operationId, or from the request path when the operation has none, so /pet/findByStatus becomes pet_find_by_status. Paths from large specs can produce long table names.

Columns come from the response schema:

  • A scalar property becomes a typed column: integers, numbers, booleans, timestamps, dates, and strings each map to the matching SQL type.

  • An object nested one level deep is flattened into parent_child columns.

  • An array of strings becomes a string-list column.

  • Anything deeper, an array of objects, or a union of types becomes a single JSON column.

The deriver skips any operation it can’t model as a table, and a skipped operation keeps its own tool. It skips an operation when any of the following applies:

  • The operation writes rather than reads.

  • The GET returns a single object or a bare scalar.

  • The response has no JSON schema.

  • The response declares more than one array, so nothing in the spec says which array holds the rows.

  • The GET requires a request body.

  • The operation requires a header the SQL tools can’t supply.

  • The row shape yields no usable columns.

  • A required parameter can’t become a filter, such as a required free-text search parameter or a required sorting, projection, or format parameter.

  • The operation takes a cursor parameter but its response declares no next-page token under a name the deriver recognizes. This case fails closed deliberately, because serving one page and presenting it as the whole table would drop rows silently.

A spec with no qualifying GET registers no SQL tools at all.

Filters and required filters

Only equality reaches the API, and how trustworthy a filter is depends on where it lands. Where a filter pushes down to a column of the response, the spec can’t declare whether the API matches exactly, case-insensitively, or by prefix, so the catalog marks that filter inexact and the API can return rows your predicate excludes. An agent that needs exact results must re-apply those filters to the rows it reads back. A filter on an echoed column is exact, because the value that comes back is the value you filtered with. The catalog marks which filters are which.

Some filters are mandatory, and a query that omits them is refused before the API is called:

  • A path parameter. Its value also comes back as a column, because a path parameter names the parent the rows were fetched under rather than a field of the rows.

  • A required query parameter.

  • An optional query parameter that declares a default. The API applies that default whenever the parameter is absent, so a query that didn’t filter on it would quietly return only part of the collection. A default that narrows nothing, such as an empty value or all, stays optional.

Free-text search parameters, such as q, query, search, or filter, never become filters, because tokenized search can drop a row whose field genuinely equals your predicate and no later check can recover it. Parameters that choose fields, sorting, or output format don’t select rows, so they never become columns either.

Page through results

A table’s page-size limit appears in its catalog entry, and execute_query returns a cursor until the scan is done. The limit depends on how the endpoint pages:

  • An endpoint that pages by cursor, offset, or page number takes its limit from the page-size parameter’s declared maximum, or from its declared default. When that parameter declares neither, or the endpoint has no page-size parameter, the limit reads as 100 and each page holds however many rows the API chooses to return.

  • An endpoint with no cursor, offset, or page parameter advertises a limit of 1000, and its body is fetched whole and sliced, so each page of results costs another full request. Keep such collections small, or hide the table.

Narrow what the SQL tools expose

Table Overrides trims the derived catalog, with at most one entry per table. Name the table exactly as list_tables returns it, then narrow it:

  • Exclude: Hide the table completely. Use it on its own, because hiding a table and narrowing it at the same time is refused.

  • Exclude Columns: Hide columns but keep the table.

  • Unfilterable Columns: Keep columns visible in the catalog and stop offering them as filters. The catalog no longer declares a filter on that column, so execute_query refuses a predicate on it and a caller has to apply the condition to the rows it reads back.

  • Required Predicates: Add filter requirements on top of the ones the table already carries. Each group must name at least one column that survives your other overrides, and a query has to filter on at least one column of every group.

Overrides only take things away. You can’t make a column filterable that the API can’t filter on, and you can’t add a table the spec didn’t produce. With an inline spec, an override naming a table or column the spec doesn’t produce is refused when you save the server. With a spec URL, nothing can be checked until the spec is fetched, so a stale override surfaces as SQL exposure switching itself off.

If a data policy masks a column that a table also declares filterable, the mask hides the column’s values but not which rows carry them, because a caller who can run execute_query can still filter on it. Run list_tables to see which columns of each table can be filtered. To close this for a column, list it under Unfilterable Columns. To close it for a caller, withhold execute_query from anyone who must not learn which records exist. See the data policy limitations.

With SQL exposure on, the two tools are available to every caller of the server, including agents. Turn off Enabled on a server whose agents should only reach the generated operation tools.

Troubleshooting

Symptom What to check

"Failed to load spec"

Confirm the spec URL is reachable from Redpanda Agentic Data Plane and serves valid JSON or YAML. CORS doesn’t matter (Redpanda fetches server-side).

Tools list doesn’t include an expected operation

The operation include or exclude filters might be excluding it. When you set an include filter on operationId, operations that have no operationId are excluded. If two operations resolve to the same tool name, the server keeps one, drops the other, and logs a warning naming both.

Tool input schema looks wrong

allOf, oneOf, and anyOf are handled by flattening: for oneOf and anyOf, all member properties are merged into a single object and every property becomes optional, so the schema doesn’t enforce which variant you pick. An OpenAPI discriminator is ignored, callbacks are not supported, and schemas deeper than max_schema_depth (default 5) are truncated. Adjust the spec or raise the depth.

The SQL tools don’t appear

Check that Enabled under Queryable is on. A server created with rpk ai or the API is not queryable unless its configuration sets it. If the setting is on, the spec may have produced no tables: SQL exposure needs at least one GET whose success response is an array of objects, or an object wrapping one. See Expose API data as SQL tables.

The SQL tools disappeared after a spec change

SQL exposure switches itself off rather than taking the server down with it, and the operation tools keep working. A Table Overrides entry naming a table or column the spec no longer produces does this, as does a spec that starts declaring an operation named list_tables or execute_query. Fix the override, or exclude the colliding operation with Filter.

execute_query refuses a query

The table requires a filter you didn’t supply. Path parameters, required query parameters, and optional parameters that declare a default are all mandatory filters. Run list_tables to see what each table requires, and which of its columns can be filtered at all. See Expose API data as SQL tables.

A SQL query returns rows your filter should have excluded

A filter that pushes down to a column of the response is inexact, because the spec can’t say how the API matches it. Re-apply that filter to the rows you read back. Filters on echoed columns are exact. The catalog marks which filters are which. See Expose API data as SQL tables.

Calls return 401

Authentication mode or credentials are wrong. Confirm secret content and the API’s expected authentication header.

Calls return 404 with the right operation

servers block in the spec doesn’t match your real API endpoint. Use the Base URL field.

Limitations

  • Custom tool logic: The OpenAPI type is purely a spec-to-tools generator. For business logic on top of the API, use a self-managed MCP server.

  • GraphQL APIs: OpenAPI doesn’t describe GraphQL. For GraphQL APIs, use a self-managed server.

  • gRPC services: As with GraphQL, use a self-managed server.

  • SQL tables are derived, not designed: The catalog reflects what the spec declares, so a thin or inaccurate spec yields a thin or inaccurate catalog. Treat list_tables as the record of what you got, and use Table Overrides to prune it.

  • SQL pushdown is equality only: Ranges, pattern matches, set membership, sorting, and server-side field selection aren’t pushed to the API. A query filters on equality, and anything else happens after the rows arrive.

  • Long SQL scans can stop early: A scan that pages by offset or page number stops after a million rows at the default page size, so an API that ignores those parameters can’t loop forever. A scan cut off this way looks complete to the caller.

  • Unpaged SQL tables re-read everything: When the endpoint offers no paging parameter, each page of results is a fresh request that downloads the whole collection and keeps one slice, and rows can shift between pages if the API’s order changes. Hide such a table unless the collection is small.