MCP Gateway
Overview
MCP (Model Context Protocol) is the standard AI agents use to discover and call external tools. In Apinizer, MCP is a first-class API proxy type — you create an MCP Gateway the same way you'd create a REST, SOAP, or LLM Gateway, and configure its behavior on a dedicated routing tab. An MCP Gateway works in one of two directions:
- Expose Tools — publishes your existing API proxy operations as MCP tools that any MCP-compatible client can discover and call
- Passthrough — forwards MCP calls through to an external MCP server, applying tool filtering and output masking along the way
The MCP Gateway menu item takes you to a card-based list of your MCP Gateways, the same layout used for LLM Gateways — open a card to reach that proxy's routing, policies, and trace/traffic/analytics tabs.
Proxy Information Fields
Each MCP Gateway's own Overview tab shows the proxy information panel every proxy type has, plus three MCP-specific fields:
| Field | Description |
|---|---|
| Routing Mode | Shows Expose Tools or Passthrough; for Expose Tools it also shows how many tools are published and which source API proxy they come from. |
| MCP Protocol Version | The protocol version this proxy serves, applied on the next redeploy — see Protocol Versions. |
| Client Authentication | None or Required — shows Required when the proxy or its attached API Proxy Group has an active authentication policy. Other policy types (e.g. IP restriction, logging) do not require client authentication; such a proxy shows None. |
Coming from an older release? MCP used to be configured on a separate "Inbound Server" screen with its own authentication settings, reachable at a dedicated path. That model has been replaced entirely — see Migrating from the Previous Model. New to the MCP protocol itself? See AI Fundamentals for a primer.
Quick Start
Create a new MCP Gateway the same way you'd create any other API proxy: give it a name and a single client-facing path.
Open the proxy's Routing tab. Select Configure on the Routing Mode panel, pick Expose Tools or Passthrough in the drawer, then select Save. The tab shows the sections for the saved mode.
For Expose Tools, select which of your API proxy operations should be published as tools. For Passthrough, add one or more MCP servers directly on the routing tab (endpoint, transport, and authentication) and, optionally, restrict which of their discovered tools are allowed through.
Attach any of Apinizer's regular authentication policies (API Key, JWT, OAuth2, ...) to the proxy — the same way you would on any other proxy type.
Deploy the proxy. Its client path becomes the single endpoint MCP clients connect to, for both discovery and tool calls.
Open the proxy's Overview tab and use the Client Endpoints section to copy the MCP endpoint plus a ready-to-run snippet for your client — see Client Endpoints (Overview).
The Routing tab has no Save button at the bottom and no "unsaved changes" warning. Every change is written to the server the moment you make it.
- Every section that holds settings — Routing Mode, Tool Output Filter, and Discovery — server.json — is shown read-only, with a Configure button that opens a right-side drawer. Change the setting in the drawer, then select Save; Cancel discards the edit.
- Each drawer — Add / Edit Tool, Add / Edit Server, and Configure for Routing Mode, the Tool Output Filter and
server.json— has its own Save button. It writes the change and closes only after the server accepted it. A rejected change leaves the drawer open with your text intact. - The few controls still shown directly on the tab are saved as soon as they change: forwarding the client token, the allowed-tool-names list, the Generate automatically / Custom selection in Discovery — server.json, and deleting a row.
- Deleting an exposed tool or a backend server row asks for confirmation first. Select Delete to remove the row — the removal is saved immediately; Cancel leaves everything as it was.
- The tab's controls are disabled for the moment a save is in flight.
- If a save is rejected, the tab returns to the last saved state. What you see is always what the server has.
- A saved change still needs a deploy to reach the gateway.
Creating One from an Existing API Proxy
If you already run a REST or SOAP API Proxy, you don't have to enter its endpoints as tool rows one by one: the Convert to MCP Gateway action generates a ready MCP Gateway next to that proxy.
The action is available in two places:
- the row menu in the API Proxy list
- the proxy's detail screen: More actions (⋯) at the top right, then Convert > Convert to MCP Gateway
It appears only for REST and SOAP proxies; MCP, A2A, AI, gRPC and WebSocket proxies don't offer it. It also requires the AI Gateway license module — without it the generated proxy would be unreachable afterward, since MCP Gateways live under the AI Gateway menu group.
Despite the name, converting replaces nothing: the source proxy does not change — its type, routing, policies, credentials, access lists and deploy state all stay as they were, and it keeps serving traffic exactly as before. The generated MCP Gateway runs in Expose Tools mode and loops every tool call back to the source proxy, so all of that proxy's policies still apply to MCP calls.
Before anything is saved, the dialog lists the tools that will be created: tool name, method, endpoint
and where the input schema comes from. The name and relative-path fields are pre-filled with the
suggested values (<source name> MCP and <source relative path>-mcp); you can change both.
How the tools are generated:
- Only active endpoints become tools. A deactivated endpoint is skipped.
- The tool name is derived from the specification's
operationId, then its summary, then<METHOD>_<path>; it is then normalized to the MCP naming convention and made unique within the catalog (_2,_3… suffixes). - For a REST source no schema is stored — the gateway derives an operation-specific schema from the
source proxy's specification on every
tools/listcall. Those rows are marked Automatic in the preview. - For a SOAP source a schema is stored, carrying a single
bodystring argument, because automatic WSDL-to-MCP schema mapping is lossy. Those rows are marked From WSDL; you can refine the schema later on the Routing tab. - If the source proxy has no active endpoint, the conversion is rejected.
- If the target name or relative path is already taken, the conversion is rejected — change it in the dialog and try again.
The generated MCP Gateway is created undeployed. Review the tool list, then deploy it to an environment the same way you would any other proxy.
To do the same from a CI/CD pipeline, use the Create MCP Gateway from an API Proxy endpoint.
Modes
Expose Tools
Publishes selected operations from your existing API proxies as MCP tools. Each exposed tool maps to one API proxy operation, can override the tool's auto-generated input schema, and can optionally set Required Scopes. Enter role names there: the caller must hold all of them, whichever authentication policy the proxy uses. The check is fail-closed — a caller who does not hold all of them, or holds no roles at all, is denied the tool. If the field is empty, any authenticated client may call the tool. A caller missing a required role is denied that particular tool while the rest of the catalog behaves normally. With the Standard MCP OAuth profile, the client asks for the OAuth Scope Name of each role — see Scopes and tool access.
How a Tool Name Is Published
The Tool Name you type on a row is normalized to the MCP naming convention before it appears in tools/list: lowercased, every character other than a letter or digit replaced with an underscore, repeated underscores collapsed, and the result truncated to 64 characters. For example Check Availability becomes check_availability and hotelDetails becomes hoteldetails. If two rows normalize to the same value, the second gets a _2 suffix and the third _3, so no tool ever disappears from the catalog.
Leave the field blank and the name is derived from the source proxy's technical id — fill it in to get a readable one.
You edit the name in the drawer that opens from the right of the row. The drawer's Save button writes the change to the server and closes the drawer once the server accepted it. The order is: Save → deploy. Skip the deploy and the table shows the new name while the gateway keeps serving the old one.
The gateway always serves the last deployed configuration. Changing a tool name and saving does not by itself change the tools/list response; the new name reaches clients only after you redeploy the proxy to that environment.
Passthrough mode does not apply this normalization — there the names come from the backend MCP server and Apinizer relays them verbatim. So a tools/list response containing a name with uppercase letters (hotelDetails, say) did not come from your Expose Tools configuration: it came either from a Passthrough configuration or from a deployment that has not been refreshed yet.
Deprecated Operations
When the source API proxy's specification marks an operation as deprecated, the tool stays in tools/list and callers can still call it. The gateway starts the tool description with [Deprecated] , so the model and the user can see it.
A disabled endpoint is a different case. Its tool also stays in the list. A call to it returns a tool error (isError is true) that says the method is not found or disabled. It is not a JSON-RPC error.
Passthrough
Forwards MCP calls to one or more external MCP servers, defined directly on the proxy's Routing tab — there's nothing to create or reference elsewhere first.
Once a server's endpoint is saved, a Discover / Reparse Tools action fetches its tool catalog and pins it as the approved baseline.
Fetch tools in the server drawer lists what the server offers right now, without saving the row — see Checking a Server Before You Save It.
Leave the allow-list empty to pass every discovered tool through, or select specific tools to restrict what's reachable through this proxy.
Each server is probed periodically and reports Healthy, Down, or Unknown status with its last response time.
Discovery and tool calls reach only public https addresses unless you turn on Allow private-network egress for a server — see Security and Governance.
Backend Failure Responses
When the MCP server fails during a Passthrough call, the gateway returns a JSON-RPC error with code -32603. The error.message field keeps the error message defined for this error type, ERR-262 (default: Tool execution failed.). The error.data field tells the client why the call failed:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32603,
"message": "Tool execution failed.",
"data": { "reason": "BACKEND_AUTH_FAILED", "phase": "discovery", "httpStatus": 401 }
}
}
phaseisdiscoverywhen the gateway could not get the tool list from the server. It iscallwhen the tool call itself failed.httpStatusappears only when the server sent an HTTP error status.backendErrorCodeappears only when the server sent a JSON-RPC error. It holds the server's error code.
reason | Meaning | What to check |
|---|---|---|
CONNECTION_CONFIGURATION_ERROR | The connection definition blocks the call. | Egress protection (private address), mTLS key store, catalog drift block. |
BACKEND_UNREACHABLE | The worker cannot reach the server. | Server address, DNS, firewall. Do not use localhost — it points to the worker pod. |
BACKEND_TIMEOUT | The server did not answer in time. | Server load and network latency. |
BACKEND_TLS_ERROR | The TLS handshake failed. | Server certificate and trust store. |
BACKEND_AUTH_FAILED | The server or its OAuth2 token endpoint rejected the credential. | Authentication settings of the server row. If token passthrough is on, the client token. |
BACKEND_HTTP_ERROR | The server sent another HTTP error status. | httpStatus and the server logs. |
BACKEND_TOOL_ERROR | The server sent a JSON-RPC error. The tool itself failed. | backendErrorCode and the server logs. |
BACKEND_PROTOCOL_ERROR | The response is not a valid MCP response, or the protocol version does not match. | The MCP Protocol Version of the server row. Make sure the address is an MCP endpoint. |
BACKEND_CONNECTION_ERROR | Another transport error occurred. | Worker log. |
The response never contains the server address, the exception text, or the server's error body. The worker log keeps the full detail in a MCP PASSTHROUGH warning line.
Both modes' tool results pass through the same output filtering used elsewhere in the gateway: a deny-pattern match blocks the whole response (not just the matched portion), and PII masking covers the platform's built-in personal-data types.
MCP Servers Are Configured Inline
There's no standalone "MCP connection" screen to visit first — an MCP server is defined at the exact place it's used:
- On a Passthrough proxy's Routing tab, where it can be one of several aggregated servers (see Multi-Target Aggregation).
- Inside the MCP Call (LLM) / MCP Call (Fixed) policy, as a single server a model calls as part of any LLM Gateway's own tool-call loop — no MCP Gateway required for that path at all.
Each definition carries its own endpoint, transport, authentication, discovered-tool catalog, health status, and approved-catalog fingerprint — nothing is shared by reference between proxies or policies, so there's no separate object to look up, and no cross-proxy blast radius when one server's credentials change.
Checking a Server Before You Save It
The Add / Edit Server drawer has a Fetch tools button next to the save row. It asks the server for its tool list there and then, using exactly the address, credential and mTLS settings currently typed in the drawer, and lists what came back — tool name and description — with a count badge.
- Nothing is saved by this. In Passthrough mode the gateway resolves the backends' catalogs at request time anyway, so the list is a confidence check ("the address and the credential work, and this is what the server offers"), not a catalog the proxy then depends on.
- When you're editing a server that's already saved, you don't have to retype its token: leave the masked field untouched and Apinizer uses the stored credential for the check.
- The button stays disabled until an endpoint is entered. It also stays disabled when you do not have manage permission on the API proxy, because the check needs that permission. If the call fails, the drawer keeps your text and states the reason the server gave — an HTTP status (
MCP server returned HTTP 404), a refused connection, a blocked egress target, or a reply that is not an MCPtools/listresult. Fix the address, the credential or the network path and select it again. - The check runs from the Manager, not from a gateway worker, so the address has to be reachable from there. With Allow private-network egress on, the check can also reach loopback (
127.0.0.1,localhost), RFC1918, link-local and in-cluster addresses, and plain http works. With it off, only public https addresses are accepted. Turning it on also opens the cloud metadata address, so enable it only for trusted servers. - A common mistake is entering the address of a non-MCP proxy (an LLM Gateway, for example). Such an address answers, but not with an MCP handshake, so the check fails with a protocol error rather than a connection error.
The Backend MCP Server List
The Backend MCP Server section lists every server on its own card: the name, the endpoint, and badges for the protocol version, the authentication scheme, private-network egress and the capability cache. Configure opens that server's drawer; the bin icon removes it.
Under each card sits that server's own tool panel. Select Fetch tools on the card and Apinizer asks that one server — with its saved address and credential, so there is no token to retype — what it offers right now, then lists every tool it answered with, name and description. The button needs manage permission on the API proxy; without it, the button is disabled.
- The list is never saved. It is live discovery, exactly like the drawer's check: the gateway asks the backend again on every request, so what you see is the answer of the moment, not a catalog the proxy keeps.
- Every tool carries an allowed or blocked badge, read from the Allowed Tool Names list in the Client Access & Forwarding section above the server list. An empty allow-list means every discovered tool is allowed. A tool the allow-list keeps out stays visible and is badged blocked on purpose — otherwise you could not tell a tool missing from the server apart from a tool merely missing from your allow-list.
- A caller-specific identity rule (see Security and Governance) can still hide an allowed tool from an individual caller. The badge reports the proxy-wide decision, not a per-caller one.
- When the server can't be reached, the panel says so and prints the reason the server gave — an HTTP status, a refused connection, a blocked egress target — so you can correct the address or the credential without leaving the page.
Routing Mode Panel
The Routing Mode panel at the top of the Routing tab is a read-only summary: the active mode's card is highlighted, the other is dimmed, and the advertised protocol version is shown next to the panel title.
Select Configure to change either of them in a right-side drawer — pick the mode, pick the protocol version, then select Save; Cancel discards the edit. The mode decides which sections the tab shows (Exposed Tools for Expose Tools, Backend MCP Server for Passthrough), and those sections move only once the save succeeds, so browsing the two descriptions in the drawer never rearranges the page underneath you. Each mode keeps its own settings when you switch, so you can move back and forth without losing what you already entered.
Picking Passthrough in the drawer also shows a Backend MCP Server list right below the two mode cards, because Passthrough is only a valid configuration once at least one backend server exists. Select Add Backend MCP Server to open the server drawer on top of the mode drawer and fill it in there; the server is saved as soon as you select Save in it, and you're returned to the mode drawer with your selection intact. Existing servers are listed with Edit and Delete next to each one. While the list is empty, the mode drawer's own Save stays disabled and a warning explains why — so you can no longer save Passthrough first and meet the "Passthrough needs at least one backend MCP server" refusal afterwards.
Tool Output Filter
The Tool Output Filter panel is read-only in the same way: a badge shows whether the filter is Active or Disabled, a second badge shows whether PII masking is on, and the deny patterns are listed below (a dash means there are none).
Select Configure to open the drawer, where you switch the filter on or off, switch Mask PII on or off, and add or remove deny patterns; Save writes all of it in one step and Cancel discards it. A deny-pattern match blocks the whole response, so a pattern that is too broad also blocks legitimate ones — prefer specific patterns.
Discovery Document
By default, an MCP Gateway's server.json document — described in Protocol Versions and in Client Endpoints below — is generated automatically from the proxy's own settings and its request-authentication policy chain, and stays in sync with them across saves and redeploys.
On the Routing tab's Discovery — server.json panel, switch to Custom to take the document over completely:
- The panel shows the published document read-only. Select Configure to open it in a right-side drawer, edit it there, then select Save — Cancel discards the edit, and Save stays disabled while the text is not valid JSON.
- The drawer is seeded with the document the gateway generates today, so switching over doesn't mean starting from a blank file — you only edit what you actually want to change.
- The drawer's Save writes the document straight to the server and closes only once it was accepted — there is no second Save button to press afterwards. A rejected document leaves the drawer open with your text intact.
- The text must still be valid JSON to save; a syntax error is flagged inline and blocks saving. A document missing a required MCP Registry member (
name,description,version, orremotes) only produces a non-blocking warning — it's your document, and Apinizer won't reject it on your behalf. ${VARIABLE_NAME}environment variables inside the custom text are resolved when the document is served, so the same hand-written text can still carry a differentremotes[].urlper environment.- To give the document back to the gateway, switch the same select button to Generate automatically. That discards the custom text and saves the change immediately; the gateway resumes generating the document from the next deploy on. There is no separate "back to automatic" link.
Because server.json is served completely unauthenticated, whatever you type is exactly what an anonymous caller receives — keep it free of secrets, and keeping it accurate afterward is entirely your own responsibility.
Client Endpoints (Overview)
The Overview tab's Client Endpoints section is where you copy the exact addresses and ready-to-run requests a client needs, scoped to an environment you pick from a selector at the top of the section. Every credential shown here — in the address list, the samples, and the numbered steps below them — is derived from the proxy's own request-authentication policy chain, not a fixed template: the section shows exactly what a caller has to send for the policies you've actually attached, nothing more and nothing assumed.
- MCP Endpoint — the single
POSTJSON-RPC address every MCP client calls forinitialize,tools/list, andtools/call, shown with a copy button. - server.json — the unauthenticated
GETaddress described in Protocol Versions, shown with a copy button, an Open link that loads it directly in a new browser tab, and a badge showing whether the document is Generated or Custom (see Discovery Document). - Token Endpoint — shown only when the attached policy chain actually issues a token (JWT or OAuth2 managed by this gateway): the
POSTaddress to call first to obtain one, with its own copy button. - Legacy SSE Stream — shown only when the proxy's negotiated protocol version is
2024-11-05; theGETaddress for that transport's push channel, with its own copy button. - Ready-to-run snippets — a tabbed set covering
tools/list,tools/call(with a tool picker so you can generate a call for any exposed tool without typing its name or arguments by hand),initialize,server.json, and a Client config tab with drop-in.mcp.jsonand Claude Desktop configuration blocks. Each sample carries whichever credential the attached policy chain actually needs — a Basic policy produces-u "<username>:<password>", an API key policy sends the configured header or query parameter, and a JWT/OAuth2/OIDC policy prefixes the sample with the token request described below.
Below the addresses, whenever the proxy has at least one request-authentication policy attached, an Authentication panel spells out the exact numbered steps for that chain — several policies stacked together (for example mTLS plus Basic) each contribute their own step, because the caller has to satisfy all of them:
- Basic — send the consumer's username and password as HTTP Basic (
-u "<username>:<password>", or anAuthorization: Basic <base64(username:password)>header). - API key — send the key in the configured request header, or as a query-string parameter if that's how the policy is set up.
- JWT / OAuth2 issued by this gateway — first
POSTthe Token Endpoint above (grant_type=client_credentialsorgrant_type=password, depending on the policy), then call the MCP endpoint withAuthorization: Bearer <access_token>. - OIDC — obtain an access token from the external identity provider configured on the policy, then send it the same way as a bearer token; Claude.ai / Claude Desktop "Custom connectors" can drive this automatically when the provider supports OAuth 2.0 Dynamic Client Registration.
- mTLS — present the client certificate on the TLS handshake (
--cert client.crt --key client.key); there's no header to copy.
Three cases can't be fully expressed in a static client configuration file, and the panel warns instead of showing a sample that would not work:
- HTTP Digest needs a challenge/response round-trip that MCP clients such as Claude Desktop and Cursor can't perform — prefer Basic or a token policy for MCP.
- An API key sent as a query parameter is appended to the URL in curl samples, but
.mcp.json/ Claude Desktop configuration has no field for it. - mTLS can't be presented by
mcp-remoteor Claude Desktop — terminate mTLS on a local bridge/proxy, or pair it with a token policy.
If the attached policy has no client-side recipe at all (for example JOSE message-level validation, or SAML), the panel says so directly instead of guessing — check the Policies tab and use a policy an MCP client can actually satisfy.
For example, given an MCP Endpoint of https://api.apinizer.com/apigateway/aigw/petstore-mcp/ protected by an OAuth2 policy (client-credentials grant, managed by this gateway), its server.json is published at https://api.apinizer.com/apigateway/aigw/petstore-mcp/.well-known/mcp/server.json, following the MCP Registry's server.json schema:
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
"name": "com.apinizer/aigw-petstore-mcp",
"description": "Apinizer MCP Gateway: Petstore MCP",
"version": "1.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://api.apinizer.com/apigateway/aigw/petstore-mcp/",
"headers": [
{
"name": "Authorization",
"description": "Bearer <access_token> obtained from https://api.apinizer.com/apigateway/auth/token",
"isRequired": true,
"isSecret": true
}
]
}
],
"_meta": {
"com.apinizer": {
"protocolVersion": "2026-07-28",
"authRequired": true,
"auth": [
{
"scheme": "bearer",
"tokenEndpoint": "https://api.apinizer.com/apigateway/auth/token",
"grantType": "client_credentials"
}
]
}
}
}
The remotes[] array is what makes the document actionable — it carries the transport, the URL, and the header(s) a caller must actually send, derived from every active policy in the request-authentication chain; the _meta["com.apinizer"] block underneath repeats the same recipe machine-readably as auth (one entry per policy, in chain order), next to the negotiated protocolVersion and a boolean authRequired flag. Nothing about the proxy's internal id, name, or routing mode is published to this unauthenticated endpoint.
The tools/list snippet looks like:
curl -X POST https://api.apinizer.com/apigateway/aigw/petstore-mcp/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Below the snippets, a collapsible Exposed tools table lists every tool the proxy currently publishes — its name, description, source API proxy, and whether it has an input schema — for a quick check before you copy a snippet.
If the source API proxy behind an exposed tool isn't deployed to the environment you've selected, that tool's input schema shows empty and a tools/call against it returns Tool backend proxy unavailable. Deploy the source proxy to the selected environment to resolve it.
Outbound Tool Call
When an LLM Gateway's model wants to call a tool (tool_use / function calling), the component that actually forwards that call to an external MCP server is the MCP Call (LLM) policy, attached to the LLM Gateway's response flow (the tool-call step). As with Passthrough, the target server is defined inline, on the policy's own screen. For a fixed, admin-chosen tool call that runs on the request or error flow instead — see MCP Call (Fixed) below.
Attach the MCP Call (LLM) policy to an LLM Gateway's response flow (the tool-call step).
In the MCP Server (Inline) section, enter the endpoint, protocol version, authentication scheme (see Backend Authentication), and capability-cache settings. It carries a single endpoint — there's no failover/multi-server list here; to call more than one external server, add a separate policy instance for each.
The Discover / Reparse Tools button connects to the server, confirms the connection works, and fetches its tool catalog — it runs before you save, so there's no separate "test connection" step.
Leave it empty to allow every discovered tool; if no tools have been discovered yet, the call is denied fail-closed. To restrict, pick from the Allowed Tools list.
This server definition is subject to the same SSRF protection as Passthrough mode (see Security and Governance). The policy screen's MCP Connection field is kept for backward compatibility: it's disabled once an inline server is defined, and only one of the two is ever used at runtime.
This policy can also be added and updated through the APIops REST API — see API Reference: Policies.
MCP Call (Fixed)
The MCP Call (Fixed) policy invokes a single, admin-chosen tool on an external MCP server independently of the model. Unlike MCP Call (LLM), it does not wait for the model to produce a tool call — it runs in the request flow or the error flow of any proxy type.
| Field | Description |
|---|---|
| Target Tool | The tool to invoke, selected from the server's discovered tool catalog. A tool not in the catalog is rejected fail-closed. |
| Arguments Template | JSON body sent to the tool. ${VARIABLE_NAME} environment variables are resolved first; #{...} context variables next. The resolved text must be valid JSON; an invalid template is treated as a dispatch failure. |
| Wait Mode | Fire and Forget (default) — dispatches asynchronously without blocking the flow. Sync to Variable — waits for the result and writes it to the named output variable. |
| Output Variable Name | Required only when Wait Mode is Sync to Variable. |
| Call Timeout (ms) | Per-call HTTP timeout. Default: 30 000 ms. |
Failure behavior. Two failure categories apply:
- Configuration problems — inline server not configured, target tool blank or its environment variable unresolved, tool absent from the catalog, or the caller's identity denied by access rules — block the request with HTTP 500 / ERR-288.
- Dispatch failures — timeout, remote error, invalid arguments JSON, or an argument constraint violation — are logged only and never block the request or error flow. In Sync to Variable mode the output variable stays empty; check it before using it downstream.
The tool result is written to the output variable without personal-data masking. Argument constraints on this policy are configured through the Management REST API only, not on the policy screen.
This policy can also be added and updated through the APIops REST API — see API Reference: Policies.
Response Flow Only
The MCP Call (LLM) policy acts on the tool calls that the model returns. These calls exist only in the response. For this reason the proxy accepts this policy, and the A2A Call (LLM) policy, only in the response flow. The platform names them "MCP Tool Call" and "A2A Agent Call" in the error. A save that puts one of them in the request flow or the error flow is rejected with the message "MCP Tool Call and A2A Agent Call policies can be placed only in the response flow". The check covers proxy-level and method-level flows and policy groups, and it applies to every save path, including the APIops REST API and import. The platform does not move the policy to another flow for you. The MCP Call (Fixed) and A2A Call (Fixed) policies are not part of this check.
Backend Authentication
The same authentication schemes are available on an MCP server row of a Passthrough proxy and on the inline server of the MCP Call (LLM) / MCP Call (Fixed) policies. The same schemes exist on A2A agents (see A2A Gateway).
| Scheme | What the gateway sends |
|---|---|
| None | No credential. |
| Bearer | A fixed bearer token in the Authorization header. |
| OAuth2 Client Credentials | A token that the gateway gets from the token endpoint with the client ID and secret. |
| mTLS (Client Certificate) | The client certificate of the selected KeyStore. No Authorization header. |
| API Key (Header) | The API key in the header you name, for example X-API-Key. No Authorization header. Authorization, Proxy-Authorization, Host, Cookie, Accept, Accept-Encoding, content and connection headers, MCP-Protocol-Version and Mcp-Session-Id cannot be used as the header name. |
| Basic Authentication | The username and password as Basic credentials in the Authorization header. The username cannot contain a colon. Use an https endpoint. |
| OAuth2 Token Exchange (RFC 8693) | A token for the target. The gateway exchanges the verified token of the caller at the token endpoint of an external authorization server. Only the new token is sent. |
Secrets in connections. The API key, the Basic password, the bearer token and the client secret of an MCP or A2A connection are hidden from a user who can only view the connection. The screen shows a "set" mark instead of the value. A user who can edit the connection sees the value. If you leave a secret field empty when you save, the stored value stays.
OAuth2 Token Exchange works as follows. The Apinizer Credential AS also accepts this grant (see Token Exchange).
- The caller token must come from an authentication policy of the proxy (JWT, OAuth2 or OIDC) that verified it in the same request. A Basic credential or an unverified header is not used.
- A request without a verified caller token is refused. The gateway never falls back to its own credential.
- Token Exchange Audience is optional. When you leave it empty, the authorization server uses its default. The gateway sends the client ID and secret with Basic client authentication.
- The gateway keeps the exchanged token in a shared cache. The cache entry lives until 30 seconds before the earlier of the token expiry and the caller token expiry. When neither expiry is known, the gateway uses the token for that call only.
- The cache key includes the caller token, the token endpoint, the client, the scope and the audience. One caller never gets the token of another.
- When the target answers 401 or 403, an MCP server entry is removed from the cache and the call is tried once more. A2A agents are not tried again.
- Token forwarding (passthrough) does not apply to this scheme.
- Fetch tools, Discover / Reparse Tools and the connection test cannot get a token, because they carry no caller token. The connection test only checks the settings. The health status of such a server shows Unknown.
Discover Auth
The Discover Auth button sits next to the endpoint on the server drawer and on the MCP policy screens. It asks the target what it needs and suggests the form values. Apply to form puts the suggestion in the form. Nothing is saved, and no credential is sent.
- The gateway sends an anonymous request to the endpoint. For a server without a fixed protocol version it starts with
server/discover, and it triesinitializewhen that fails. - When the target answers 401 with a
resource_metadataaddress, the gateway reads the protected resource metadata (RFC 9728) and then the authorization server metadata (RFC 8414, then OpenID Connect). - The form shows the suggested scheme, the token endpoint, the scopes, the grant types and the client authentication methods. You enter the client ID and secret (or the key or password) and save.
What you should know about the result:
- The gateway does not suggest a token endpoint when the issuer in the metadata differs from the authorization server address. A note explains why.
- A note appears when the resource in the metadata differs from the endpoint, when the authorization server has no client credentials grant, when it supports token exchange, and when the target needs a client certificate.
- A value that would be read as a variable, and a token endpoint that is not https, are dropped. A note tells you.
- When the target answers without credentials, the current scheme stays, because the server may protect only the tool calls.
- The discovery runs in the Manager, not in a gateway worker. It accepts https addresses; plain http is accepted only when Allow private-network egress is on. It never follows a redirect, and the whole run has a 30-second limit.
- Without Allow private-network egress, private and reserved addresses (loopback, link-local, cloud metadata, RFC1918) are refused. With it on, none of them is blocked and plain http is accepted. Only a system administrator can turn that setting on.
- Discover Auth needs the manage permission of AI development or API management in the project.
Protocol Versions
An MCP Gateway uses one protocol behavior, set per proxy on the Routing tab (Routing Mode panel → Configure → MCP Protocol Version → Save) and applied on the next redeploy. This page calls the versions up to 2025-11-25 legacy and 2026-07-28 modern.
| Version | Behavior |
|---|---|
2024-11-05 | Legacy HTTP+SSE transport — a GET to the proxy's SSE sub-path opens a bounded push channel. |
2025-03-26, 2025-06-18, 2025-11-25 | Streamable HTTP. The three behave the same on the inbound side. Responses carry an MCP-Protocol-Version header confirming the negotiated version. |
2026-07-28 | Default. Modern protocol. The client sends the protocol version with every request, so it needs no initialize handshake. See Modern Protocol (2026-07-28). |
A blank value uses 2026-07-28. Convert to MCP Gateway creates a proxy with this default.
If a connecting client requests a version Apinizer doesn't recognize during initialize, the proxy falls back to the nearest earlier version it does support and logs a warning, rather than rejecting the request outright.
Modern Protocol (2026-07-28)
Only a proxy set to 2026-07-28 reads the modern members of a request. Every other proxy handles every request as before.
- Version in each request. A modern client writes the protocol version in the request metadata (
_meta). The gateway serves the request without a session. server/discover. Returns the supported versions and the capabilities of the gateway. A legacy request gets the normal "method not found" answer.- Unsupported version. The gateway answers with the JSON-RPC error
-32022. The error data lists the supported versions and the version that the client asked for. - Unsupported version: HTTP status. The
-32022answer comes with HTTP400. - Standard headers. A modern request must carry the
MCP-Protocol-Versionheader (equal to the version in_meta), theMcp-Methodheader (equal to the method) and, ontools/call, theMcp-Nameheader (equal to the tool name). When one of them is missing or does not match the body, the gateway answers HTTP400with the JSON-RPC error-32020. Only a legacy request, one without_meta, is not checked. The gateway sendsMcp-MethodandMcp-Nameto a modern backend server. - No
_metain the request. The gateway handles the request as a legacy request. Aninitializerequest on a2026-07-28proxy gets a legacy version in the answer.
A modern tools/call can also end in an input request or a task. Both need an environment that has a JWT signing key, because the gateway signs the values that it gives to the client. Without the key, the gateway refuses these calls with the JSON-RPC error -32603.
Input Requests (input_required)
In Passthrough mode, a modern backend server can answer a tool call with input_required instead of a result. The answer lists what the server needs from the user. The client collects the values and sends the same tools/call again, with the answers and the requestState value that it received.
- The gateway passes the answer to the client. It signs the
requestStatevalue together with the caller, the client, the proxy, the backend server, the tool name and the arguments. The value is valid for 10 minutes. - When a different caller, tool or argument set uses the value, the gateway rejects the call with
-32602. - The gateway passes the answers and the backend state to the backend server unchanged.
Tasks
A task lets a client start a long tool call and read the result later. The gateway supports the Tasks extension (io.modelcontextprotocol/tasks):
- A client must declare the extension in the request metadata of the same request. A modern client that did not declare it gets the error
-32021("Missing required client capability") fromtasks/get,tasks/updateandtasks/cancel. A legacy request gets "method not found". tools/callcan answer withresultType: "task"and ataskId. The client reads the state withtasks/get. The gateway also usestasks/updateto pass user input to a backend task andtasks/cancelto cancel.- A task id that does not exist, has expired or belongs to another caller gets the same error (
-32602, "Task not found").
Three kinds of task exist:
| Kind | When it appears | Where the state lives |
|---|---|---|
| Backend task (Passthrough) | The backend server answers with a task, and the client declared the extension. The gateway signs the task id and passes it on. A client that did not declare the extension gets an error instead. | The backend server. The gateway keeps no state. The finished result passes the output filter of the proxy. |
| Gateway task (Expose Tools) | A client that declared the extension calls a tool, and the call is not finished in 10 seconds. The same call goes on in the background and is not started again. | The gateway. |
| Approval task | A call waits for approval. See Tool Risk Policy and Approval. | The gateway. |
Gateway task rules:
- A client that did not declare the extension always gets a normal synchronous answer from a slow call. The gateway never turns that call into a task.
- The gateway writes the task record before it answers. If it cannot write the record, it waits for the call and answers synchronously.
- A gateway task is limited to 150 seconds. A task that still runs after that becomes failed. The gateway does not run it again, for example after a worker restart. The suggested poll interval is 2 seconds.
- The gateway keeps a finished task for 7 days, and then removes it. It removes the stored arguments when the task finishes.
- Background calls count against the same limit of concurrent tool calls as other calls. When the limit is full, the gateway does not start a task and answers with the session-limit error.
tasks/cancelmarks the task as cancelled. It does not undo what the call already did. A call that is already running can complete its side effects.
Elicitation
Elicitation lets a backend server ask the user for more input during a tool call. The gateway supports it only between two parties of the same era:
| Client | Backend server | Form mode | URL mode |
|---|---|---|---|
Modern (2026-07-28) | Modern | Supported. The backend answers with input_required, and the gateway passes it to the client (Input Requests). | Same path. The gateway passes the content of inputRequests unchanged. |
Legacy (up to 2025-11-25) | Legacy | Supported in Passthrough mode when Relay elicitation requests is on for that backend server (off by default). | Not supported in this version. |
| Modern | Legacy | Refused with ERR-310. | Refused with ERR-310. |
| Legacy | Modern | Refused with ERR-310. | Refused with ERR-310. |
| Any | Expose Tools mode | Not applicable. The tools of an Apinizer API proxy do not ask for input. | Not applicable. |
The gateway does not handle the form mode and the URL mode in different ways. It relays the content of the request and of the answer as the backend and the client sent it. Whether a client or a backend supports the URL mode depends on their protocol version and on the elicitation capability that the client declares. The gateway tells a legacy backend only that the client can answer elicitation requests (elicitation: {}), which means the form mode. It does not announce the URL mode, because the capability of the client does not reach the backend. Between legacy parties, the URL mode is not supported in this version.
Known limit: the gateway does not produce the Mcp-Param-* headers that the tools of a backend server define with x-mcp-header.
A call that does not use elicitation is not affected by these limits.
Relay elicitation requests is a switch on the backend server drawer (Passthrough). When you turn it on:
- The gateway tells the backend server that the client can answer questions. Without the switch, the gateway sends no such capability and nothing changes for the server.
- The question goes to the legacy client in an event stream. The client answers with a separate request. This request can reach another worker. The gateway sends the answer to the backend server.
- The tool call can wait up to 5 minutes for the answer. The wait holds the request for that time.
- The gateway relays only the elicitation request. Other requests from the server get "method not found", and the call continues. A ping gets an empty answer.
- The relayed question carries a signed id that is bound to the caller and the proxy. The answer of another caller is refused.
Error Messages for MCP Calls
| Error code | When | JSON-RPC error |
|---|---|---|
| ERR-309 | A tool call needs approval, and the client did not declare the Tasks extension. The error data holds the approvalId. | -32021 |
| ERR-310 | The tool asked for user input, and the gateway cannot relay it between the protocol versions of the client and the server. | -32603 |
The gateway returns these errors as JSON-RPC errors in an HTTP 200 response, as it does for the other MCP refusals. You can change the default text on the Error Messages screen. See Error Messages.
The server.json document is published, unauthenticated, at the proxy's client path plus /.well-known/mcp/server.json, following the MCP Registry's server.json schema — required so a client can see not just that the gateway exists but where to connect and with which transport, before it has credentials to authenticate. By default it's generated from the proxy's own settings and its request-authentication policy chain; you can hand-write it entirely instead — see Discovery Document. It's a best-effort discovery aid: whenever the proxy's request policy chain contains an authentication policy, each entry in the document's remotes[] array carries the header(s) that chain actually requires, and the same recipe is repeated machine-readably as _meta["com.apinizer"].auth, next to a boolean _meta["com.apinizer"].authRequired — but the actual scheme enforced is whatever policy chain you've configured (see Authentication). The Overview tab's Client Endpoints section surfaces this same address with a copy button and an Open link, and — for a 2024-11-05 proxy — the legacy SSE address as well, so you don't have to build either address by hand.
Deployment Information
An MCP Gateway's Deployment Information table (see Deployment and Specification Information) carries two MCP-specific columns in addition to the fields every proxy type shows:
| Column | Description |
|---|---|
| server.json | A link to the server.json document published for that environment. |
| Protocol | The MCP protocol version negotiated for that environment's deployment. |
Clicking Show in the Specs column opens a dialog with four read-only tabs for that environment — tools/list, initialize, server.json, and Client config — instead of an OpenAPI document. An MCP Gateway has no REST/SOAP contract to display, so the dialog shows the same catalog, handshake, and server.json document a connecting client would actually see, plus a ready-to-use client configuration block.
Multi-Target Aggregation
A Passthrough proxy isn't limited to a single backend: define up to 10 MCP servers on its Routing tab, behind one client-facing endpoint. Their tool catalogs are merged into a single list; if two servers expose a tool with the same name, the collision is resolved deterministically by prefixing the tool name with its source server (serverName__toolName). If one server is slow or unreachable, the others still respond — a single failing target doesn't take down the whole catalog.
Security and Governance
Authentication
Authentication for an MCP Gateway is whatever policy chain you attach to it — API Key, JWT, OAuth2, Basic, and so on — the same policies every other proxy type uses. There's no separate per-server auth-mode selector anymore; the proxy's own policy chain runs before a request ever reaches MCP handling. The one deliberate exception is server.json itself, which stays reachable without authentication — it only advertises the requirement (through remotes[].headers and _meta["com.apinizer"].auth/authRequired), it never enforces it; enforcement is always the policy chain's job. The exact client-side recipe — which header, which token endpoint, which curl flag — is derived per policy type and spelled out step by step in the Client Endpoints section.
Standard MCP Client Profile
Standard MCP clients (for example MCP Inspector or Claude Code) find the authorization server by themselves. They read the protected resource metadata of the MCP server, read the metadata of the authorization server, get a token for this MCP resource and then call the tools. The Standard MCP OAuth profile makes the gateway work this way. The default profile is Legacy. It keeps the behavior of earlier versions.
You select the profile in the Routing Mode panel on the Routing tab (Configure → Client Authentication Profile). The screen shows the prerequisites and the reason for each problem. It does not refuse the save or the deploy when the authentication chain is not ready yet. You can select the profile first and add or finish the policy later. While the chain has a problem, the gateway does not publish the protected resource metadata, so MCP clients cannot sign in. The deploy still needs an access URL for the environment.
Upgrade all workers of the environment before you select the Standard profile. An older worker does not publish the protected resource metadata, and it accepts a standard token on a Legacy proxy.
Prerequisites
- The authentication chain of the proxy uses tokens from one authorization server: an OAuth2 or JWT policy with Manage From ACL (the Apinizer Credential AS), an OAuth2 or JWT policy with Manage From This Policy and the Browser login (authorization_code) switch on (the Apinizer
/authauthorization server), or an OIDC policy (one identity provider). - The environment has an access URL. It defines the canonical resource.
- In Expose Tools mode, the proxy has a service client, and each tool has a Downstream identity (see below).
The screen reports these problems:
| Problem | Meaning |
|---|---|
| No active authentication policy | Add an OAuth2 or JWT policy with Manage From ACL, or an OIDC policy. |
| Policy issues proxy key tokens without browser login | Turn on the Browser login (authorization_code) switch in the policy, or select Manage From ACL. |
| OIDC policy without an issuer | Set the issuer of the identity provider. |
| OIDC policy does not accept a Bearer token | Turn on Bearer JWT authentication in the policy. |
| OIDC policy does not verify the JWT signature | Turn on local JWT validation and signature validation (JWKS). |
| Policy asks for a credential that is not a token | Remove the policy (Basic, API key, Digest, mTLS, SAML or JOSE policies, for example). |
| Tokens from more than one authorization server | Keep only one. |
| A JWT policy and an OAuth2 policy in the same chain | Keep only one. |
The chain is read from the policies of the proxy itself. A proxy group policy runs only for a request that comes through the group path.
Canonical resource
The canonical resource is the environment access URL, the Apinizer root context and the proxy path, without a trailing slash. The gateway builds it from the configuration. It never uses the Host or X-Forwarded-* headers of a request. Tokens are issued for this value and are accepted only with this value in aud.
Protected resource metadata
The gateway publishes the document (RFC 9728) without authentication at the proxy path plus /.well-known/oauth-protected-resource. It contains the resource, the one authorization server (authorization_servers), the scopes that clients can ask for (scopes_supported) and bearer_methods_supported (header). The gateway publishes it only when the authentication chain accepts the Bearer token of a single authorization server and the Standard profile fits the chain. When the gateway has nothing true to publish, the address answers 404. This includes a chain that still has a problem from the table above. Check the prerequisites panel on the Routing tab. A Legacy proxy does not publish it, and the request goes through the normal authentication chain.
The document is under the proxy path only. If you need the address in the RFC 9728 "path insertion" form, the ingress must provide it. A browser-based client such as MCP Inspector needs the CORS settings of the proxy to be open.
The gateway answers an unauthorized request with a standard WWW-Authenticate header that names this document:
| Situation | Answer |
|---|---|
| No token | 401 with Bearer resource_metadata="…". |
| Invalid token | 401 with error="invalid_token" and resource_metadata. |
| A tool needs a scope the token does not have (Expose Tools) | 403 with error="insufficient_scope", the scope the client can ask for, and resource_metadata. |
Authorization server metadata
- Apinizer Credential AS (Manage From ACL). The issuer is
<access URL>/credential. The canonical metadata (RFC 8414) is athttps://<host>/.well-known/oauth-authorization-server/credential. The older/credential/.well-known/openid-configurationand/credential/.well-known/oauth-authorization-serveraddresses keep working. The metadata lists the token endpoint, the keys, introspection and revocation, theclient_credentialsand token exchange grants, and the client authentication methods. The issuer comes from the configured access URL, and it is equal to theauthorization_serversvalue of the protected resource metadata. When the access URL of the environment is defined, the metadata also advertisesauthorization_endpoint, theauthorization_codegrant, PKCES256and thenoneclient authentication method. - Apinizer
/authauthorization server (Manage From This Policy). When the Browser login (authorization_code) switch is on in the policy, the issuer is<access URL>/auth. The canonical metadata is athttps://<host>/.well-known/oauth-authorization-server/auth. The older/auth/.well-known/oauth-authorization-serveraddress keeps working. When the switch is off, the proxy cannot use the Standard profile. The screen reports this. - Identity provider. The identity provider publishes its own metadata. The policy issuer is the address that the protected resource metadata names.
Getting a token
- Machine to machine (Credential AS). The client sends a
client_credentialsrequest to/credential/tokenwith oneresourceparameter (RFC 8707) that equals the canonical resource of the proxy. The token is for this resource only (aud). Its format (JWT or opaque) follows the policy of the proxy. This request gets no refresh token. A request withoutresource, with another grant type or with an unknown resource works as before and gives a legacy token. The Standard proxy refuses a legacy token, and a Legacy proxy refuses a standard token. - Browser login (Apinizer authorization server). The client follows the authorization code + PKCE flow with a public API client. The
authorizeandtokenendpoints are under/credentialin Manage From ACL mode and under/authin Manage From This Policy mode.resourceis required and must equal the canonical resource of the proxy. Theaudof the access token is this resource. If it is a JWT, its header hastyp: at+jwt. The refresh token rotates on every use, and the replay of a used refresh token revokes the token family. The access of the user and the access of the application are both required: access of the user ∩ access of the application. For the setup, the limits and the check with curl, see MCP Gateway Browser Sign-In Without Keycloak. - Identity provider. The client registers with the identity provider and gets a token there. The
audclaim of the token must contain the canonical resource. Some identity providers ignore theresourceparameter. For example, Keycloak needs an Audience mapper that adds the canonical resource toaud. The gateway verifies the JWT locally with the keys (JWKS) of the provider. It reads the token only from theAuthorizationheader. It does not accept an opaque token from the provider. The gateway does not read roles from thescopeclaim, and it does not read an array inscp.
Client ID metadata documents (CIMD) and dynamic client registration (DCR) are not in this release. The metadata does not advertise them (client_id_metadata_document_supported is absent), and a client_id in the form of an https URL is refused as an unregistered client (invalid_client). With an identity provider, the provider registers the client. At the Apinizer authorization server, the client is an API client that you create beforehand: a confidential client for a machine client, and a public client for browser login.
Scopes and tool access
In the Standard profile, the scope in the token is a wire scope. A role gets its wire scope in its OAuth Scope Name field. A client asks for the scope name, not for the role name. A caller can use a tool only when both are true: the token contains the wire scope of the role that the tool needs, and the user holds that role now. A role without an OAuth scope name opens no tool. The scope name must be unique among the roles that a client of the project can hold. The Identity Access Rules apply in addition.
Calls to the tool target
In the Standard profile, the gateway does not pass the Authorization header of the caller to the target of an Expose Tools tool. It creates the token for the target itself. You set this for each tool in Downstream identity:
- Service. The gateway calls the target as the service client of the proxy. The user is recorded only for audit.
- Delegated user. The gateway calls the target as the signed-in user, acting through the service client. The call carries both. The target must accept delegated tokens, and the service client must be in its Allowed agents list. The gateway uses the token exchange grant of the Credential AS in-process. See Token Exchange. A user from an identity provider is delegated only when the provider maps the user to an existing API client. Otherwise the call is refused.
A tool argument or a client header cannot set the identity of the call: the gateway removes the Authorization value of the caller and any argument that is defined as a header.
User context in logs and limits
For a Standard profile call, the traffic log carries the user and the client of the token:
| Log field | Value |
|---|---|
usub | The stable user ID: the sub of an identity provider token. For a Credential AS client_credentials token, the client itself. For a browser-login token, the username of the signed-in user (in Manage From ACL mode, the username of the consumer). |
uids | The identity source: the issuer of an identity provider token, apinizer for a Credential AS token and for a browser-login token from Manage From ACL mode (/credential), or apinizer-policy for a browser-login token from Manage From This Policy mode (/auth). |
ocid | The OAuth client that got the token (azp, else client_id). |
The result class of a tool call (mcprc) is separate. See Logging and Analytics. A Limit Plan can use the OAuth client with the context value OAuth Client ID (standard MCP token). The user stays the authenticated identity, so the user chain of the Limit Plan works as for other proxies.
In the Standard profile, the prerequisites panel on the Routing tab shows the canonical resource, the authorization server (issuer) and the Browser login (authorization_code) row. The row reports one of three states: on (users of the identity source of the policy sign in on the login page), available (you turn it on in the API client of each application), or not offered by this authorization server.
Step-by-step examples: MCP Gateway Sign-In with Keycloak (external identity provider) and MCP Gateway Browser Sign-In Without Keycloak (Apinizer authorization server).
Identity-Based Access Control
Optionally, restrict tool visibility by the caller's consumer or API client, role, or organization. The rules apply in both modes. In Expose Tools mode a caller must also pass the tool's own required-scope check (see Expose Tools). Each rule allows or denies specific tool names for the principal it matches; once at least one rule is configured, a caller not covered by any rule sees nothing in the tool list (default-deny) and can't call any tool either. A name in a deny list always wins. You set the rules in the Identity Access Rules drawer on the Routing tab, or through the Management REST API:
{
"identityAcl": [
{
"principalType": "ROLE",
"principalName": "partner-readonly",
"allow": ["search", "fetch"],
"deny": []
}
]
}
Argument Constraints
Attach validation rules to a tool's arguments. The gateway checks the rules before it sends the call, and it rejects the call when a rule fails.
You set the rules on the Routing tab:
- Expose Tools: open the tool in the tool drawer and use its Argument constraints editor.
- Passthrough: use the Argument constraints panel. Add a tool and enter its name exactly as
tools/listshows it. The match is case-sensitive. A tool that two servers use appears asserver__tool.
Each rule has an Argument path, a Rule type and a Value:
| Rule | Value | The call is rejected when |
|---|---|---|
| Regex (full match) | A regular expression | The argument does not match the whole expression. |
| Maximum length | A whole number, 0 or greater | The text of the argument is longer. |
| Allowed values | Values separated by commas | The argument is not one of the values. |
| Required | None | The argument is missing. |
- The argument path is a list of property names separated by dots, for example
user.email. Array indexes (items.0.name) and wildcards (*) are not supported. - Regex, maximum length and allowed values check an argument only when it is present. Add a Required rule to make an argument mandatory.
- The gateway skips a saved rule that is not valid (an empty path, a regular expression that does not compile) and does not block the traffic. The screen does not save such a rule, so you see the problem before the save. Environment variables (
${var}) are resolved at run time and are not checked on the screen. - Regular-expression rules run under a hard ceiling (a 4096-character input cap and a 200ms evaluation deadline) so a crafted value can't turn a rule into a denial-of-service vector.
The same rules are available as JSON in the Management REST API:
{
"argumentConstraints": [
{
"toolName": "search",
"rules": [
{ "argumentPath": "query", "ruleType": "MAX_LENGTH", "value": "500" },
{ "argumentPath": "region", "ruleType": "ENUM", "value": "eu,us,apac" }
]
}
]
}
Limiting Tool Call Volume
The MCP routing has no call quota of its own. You limit call volume in two places, and each place has one job:
- Limits for an identity (per consumer, API client, application or organization) — set them in a Limit Plan.
- Protection of the proxy (the total of all callers) — add the Quota or Throttling policy to the proxy.
Both can count MCP tool calls separately for each tool. Select the context value MCP Tool Name (tools/call) as the counting variable. It is the canonical tool name, the same name that the gateway uses to run the call, so my_tool, MY_TOOL and my-tool use one counter. Do not read $.params.name from the request body for this. The body holds the name as the client wrote it, and each spelling then gets its own counter.
A callQuota value saved by an earlier version is ignored, and the next save of the routing removes it.
Tool Risk Policy and Approval
The tool risk policy gives every tool call a risk tier and sets what the gateway does for each tier. It works in both modes and is off by default. While it is off, every call is allowed and no tier is computed.
Risk tiers
A tool gets one of three tiers: Low, Medium or High. The gateway takes the first rule that matches:
- Your setting. In Expose Tools mode, the Risk tier field of the tool. In Passthrough mode, a row in Tool risk tiers for the tool name as
tools/listshows it. - Trusted tool annotations. When Trust tool annotations is on for the backend server: a read-only tool is Low, a destructive tool is High, and any other tool is Medium.
- Untrusted tool annotations. When Trust tool annotations is off (the default), the annotations can only raise the tier. A destructive tool is High. A read-only mark does not lower the tier. When a tool is marked both read-only and destructive, it is High.
- Default. In Expose Tools mode, the HTTP method of the operation: GET and HEAD are Low, DELETE is High and other methods are Medium. In Passthrough mode, Medium.
A tool without annotations keeps the default tier. Turn Trust tool annotations on only for a server that you control.
Actions
For each tier, you choose one of three actions:
| Action | What the gateway does |
|---|---|
| Allow | The call goes on. |
| Require approval | The call waits until a manager approves it. |
| Deny | The gateway rejects the call. |
When you turn the policy on, the screen starts with Low = Allow, Medium = Allow and High = Require approval. You can change each one.
Exemptions are identity rules (consumer or API client, role or organization). For a matching caller, Require approval becomes Allow. Deny does not change. The exemption checks only the identity, so it applies to every tool.
The risk check runs after the scope and identity rules, and the argument constraints, and just before the call is sent.
Approval flow
- The call needs approval. The gateway writes an approval request and answers the client at once:
- A client that declared the Tasks extension gets a task in the state "waiting for approval".
- Any other client gets the error
-32021with anapprovalId(ERR-309).
- A manager approves or rejects the request in Pending tool approvals (see below).
- After the approval, the client reads the task with
tasks/get, or sends the same call again. The gateway then runs the call one time. Nothing runs the call by itself. - A new attempt of the same call before the decision does not create a second request. The same caller can hold at most 20 open approval requests on one proxy. A further call is refused.
Rules for the approval:
- 15 minutes. The request is valid for 15 minutes from the moment of the request. The decision and the single run must both happen in that time. After that the request expires.
- Rejected. For 15 minutes after the rejection, the same call is refused with a policy error. After that, a new attempt creates a new request.
- Bound to the call. The approval belongs to the caller, the client, the proxy, the tool and the exact arguments. It also belongs to the operation that the tool resolves to: in Expose Tools mode the target proxy, the HTTP method and the path; in Passthrough mode the backend server and its tool name. If any of these has changed when the call runs, the gateway does not run it, and the task fails.
- Checked again at run time. The gateway checks the scope, the identity rules and the risk policy again when it runs the approved call. A policy that now denies the tool stops the call.
- Not an approval. An answer to an elicitation or the opening of a link is never an approval. Only a manager in the Manager makes the decision.
- Anonymous proxies. When the proxy has no authentication, the gateway cannot tie the approval to an identity. Every call with the same arguments uses the same approval. Use the risk policy with an authentication policy.
Pending tool approvals
The Pending tool approvals panel on the Routing tab lists the requests that wait for a decision. It shows the tool, the risk tier, the caller, the client ID, the operation (a summary of the resolved target), the request time and the expiry time. Select Approve or Reject and confirm.
- The panel shows the operation summary and the tool. It does not show the argument values.
- You need the manage permission of AI development or API management in the project.
- The approval and rejection decisions made in the Manager are written to the audit log.
- The gateway records the other outcomes (a policy rejection, an expired request, a changed target or arguments) in the state of the task record, in the Tool approval field of the traffic log, and in a warning line of the gateway log. These outcomes are not in the audit log.
Traffic log and alarms
- The traffic detail of a call shows Tool approval with the state: Pending, Approved, Rejected or Expired. The same value is a log field named
mcpap. The Elasticsearch template carries it after the upgrade. - Two alarm trigger types watch the approvals: MCP Tool Approval Pending Count (the requests that wait now) and MCP Tool Approval Rejected Count (the rejections of the last hour). Both use the Project scope. See Alarms.
Catalog Drift Protection
Each inline MCP server remembers a fingerprint of its tool catalog captured at the moment it was last approved (an explicit Discover / Reparse, or the first discovery at deploy — see Catalog Discovery at Deploy). A periodic health check compares the live catalog against that fingerprint; when they diverge, the server either just logs and surfaces the drift (the default) or blocks every tool call through it until an operator re-approves the new catalog. This guards against a backend server silently changing what a tool does, or what it expects, underneath an already-approved integration.
Catalog Discovery at Deploy
An inline MCP server of an MCP Call (LLM) or MCP Call (Fixed) policy that has no tool catalog and no approved fingerprint yet gets its first catalog when you deploy the proxy.
- On deploy, the Manager asks the server for its tool list once, before the worker receives the proxy.
- All probes of one deploy run in parallel. One total time limit of 30 seconds applies to the whole deploy.
- A successful first result is saved as the tool catalog and becomes the approved baseline. Later changes are checked against it (see above).
- A server that already has a catalog or an approved fingerprint is not probed again. A drift that was detected is not approved by a deploy.
- A server that returns no tools, cannot be reached, or does not answer within 30 seconds does not stop the deploy. The deploy result shows a WARNING line for it, and its tool calls stay refused until a catalog is discovered. Use Discover / Reparse Tools and deploy again.
- A server whose authentication scheme is OAuth2 token exchange is not probed at deploy. There is no caller token at deploy time, and the scheduled health check skips it for the same reason. The same applies to an inline A2A agent with this scheme. No WARNING line is written for it. Use Discover / Reparse Tools to get its tool catalog.
- The servers in the Routing tab of a Passthrough proxy are not probed at deploy.
Token Passthrough
By default, Apinizer never forwards a caller's own token to a backend MCP server. The gateway credential on the server row is used instead. In a zero-trust setup, the backend MCP server must authorize the real caller. For this, turn on caller forwarding in Passthrough mode.
Forward client token to backend server (Routing tab → Backend MCP Server → Client Access & Forwarding → Configure):
- When on, the caller's
Authorization: Bearertoken goes to the backend MCP server on every call:initialize,tools/listandtools/call. The gateway credential of the server row is not sent. - Accepted token audiences is optional. When filled, only a JWT whose
audclaim matches an entry is forwarded. We recommend it: it makes sure a token issued for this MCP server reaches only this MCP server (RFC 8707 resource indicators). - When no token can be forwarded (the caller sent none, or the audience did not match), the gateway credential of the server row is used. The call is never sent without authentication.
Forwarded client headers (Routing tab → Backend MCP Server → Client Access & Forwarding → Configure):
- The gateway copies the listed headers from the client request to the backend MCP server on every call, for example
app-identifierorx-tenant-id. Names are case-insensitive. - A header the client does not send is not forwarded. Header forwarding works with or without token forwarding.
- These headers cannot be forwarded:
Authorization,Proxy-Authorization,Host,Connection,Keep-Alive,Transfer-Encoding,TE,Trailer,Upgrade,Content-Length,Content-Type,Content-Encoding,Accept,Accept-Encoding,Mcp-Session-Id,MCP-Protocol-Version. Use token forwarding for the caller token. The list can hold at most 20 headers. - A header value with a line break, or longer than 8 KB, is not forwarded.
Required headers. Select Required on a header row to make the header mandatory:
- The gateway checks every JSON-RPC request in Passthrough mode:
initialize,tools/list,tools/calland other methods. The discovery document and the SSE endpoint are not checked. - When a required header is missing, the gateway does not call the backend MCP server. It returns HTTP 400 with
Content-Type: application/json. A blank header value counts as missing. When more than one required header is missing, the first one in the table is reported. - The traffic log shows the request as Blocked.
- The check works with or without token forwarding. A present header is still forwarded.
The Error message column shows Default or Custom. Select the edit icon to change the response body. The body must be valid JSON. Reset to default restores the default body:
{"jsonrpc":"2.0","id":{{jsonRpcId}},"error":{"code":-32600,"message":"Missing required header: {{headerName}}"}}
You can use two placeholders in the body:
{{headerName}}— the header name as configured.{{jsonRpcId}}— theidof the JSON-RPC request, written as JSON. A number stays a number, a string gets quotes, and a missingidbecomesnull. Do not put this placeholder in quotes.
With forwarding on, the tool list cache is kept per caller. One caller's tool list is never returned to another caller.
Fetch tools on the server row uses the gateway credential of the row. When client token forwarding is on and the row has no credential (Authentication Scheme = None), the row shows Client token required instead of fetching: the tool list appears when a client calls the gateway with its own token. Test such a server with a real client call.