[Proposal] MCP Server for the API Platform Gateway Controller #3217
Thinal-Fernando
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Overview
Currently operators of the WSO2 API Platform gateway manage APIs, credentials, policies, certificates and LLM gateway resources through raw REST calls or the console. AI agents have no supported way to perform these operations.
This post proposes an MCP server built into the Gateway Controller, exposing its Management API and read-only Admin API as MCP tools at
<gateway-controller-host>/mcpover streamable HTTP.Two central design decisions are:
management-openapi.yamland loaded at startup, rather than being configured separately or hardcoded in the tool layer.Tool surface
An earlier iteration mapped one backend operation to one tool, producing 85 tools across 8 toolsets, plus 28 chained tools for order-sensitive workflows. The current design reduces the resource-management surface to 8 tools will still covering all 43 controller operations:
Why now
Agent-driven administration removes a large amount of manual console and REST toil. It also aligns the API Platform with the direction adjacent WSO2 products are already taking; OpenChoreo exposes its surface over MCP - and with competitors adding agent interfaces.
Core Design Principles
deploy_api,list_resourses), not for which backend endpoint serves it. The resource kind is a parameter, not part of the tool identity.kindon its request schema, so a single tool can accept any manifest, read the kind, and dispatch. The model never guesses a kind it was not given.confirmargument; a missing or non-truevalue refuses the call.Architecture
Authorization
The spec is the source of truth
Tool → allowed-scopes is loaded from the embedded OpenAPI spec at startup. Nothing is configured per-tool in
config.toml.Operation-backed tools carry
x-mcp-toolnext to their existingx-basicauth-roles:The structured form matters for consolidated tools: one MCP tool maps to many operations, so
kindandverbare what let the loader work out which operation's roles apply to a given call.Helper tools with no REST operation are declared in a root block:
A tool with no entry is denied to everyone, the map is an allowlist. At startup the server checks every registered tool has a mapping and refuses to boot if one does not: an unreachable tool is a configuration bug worth failing fast on.
x-basicauth-rolesis an explicit allow-list, so a token qualifies if it holds any one of the listed scopes. A non-contiguous set such as[admin, consumer]must not admitdeveloper, which a tier ordering would wrongly do.Why enforcement happens twice
AuthorizationMiddlewareat the MCP protocol layer is authoritative - nothing executes without scope. But an MCP-layer denial becomes a JSON-RPC error inside HTTP 200, and clients read that as "the tool failed" and never re-authorize.To make a client re-run the OAuth flow asking for the missing scope, the server has to answer the HTTP request itself:
Only an
http.Handlercan do that, henceScopeGate. The three challenge parameters each do a job:error=insufficient_scopetells the client to re-authorize rather than refresh;scope=names what to ask for, so the retry isn't identical to the first attempt;resource_metadata=lets a client that lost its discovery state rebuild it.If the gate is ever bypassed,
AuthorizationMiddlewarestill denies - the gate is an affordance, not the control.tools/listis deliberately unfilteredEvery tool is advertised regardless of the caller's scopes. Filtering would make the step-up flow unreachable: to receive the 403 and re-authorize, the client must be able to see and attempt a tool it currently lacks scope for. Authorization happens on the call, not by omission from the list. (Flagged for review - see Open Decisions.)
Example
deploy_apiwith aRestApimanifesttools/callarrives with a manifest declaringkind: RestApi.ScopeGatepeeks the payload, resolvesdeploy_api→[admin, developer], and checks the token's scopes.AuthorizationMiddlewarerepeats the check against the per-call token.RestApi; class checked (routable ✓).RestAPIService.Create.On failure at step 2 the client receives the 403 challenge above and can re-authorize for
adminordeveloperwithout restarting the session.The Manifest as the Source of Truth
Five of the six manifest kinds carry a k8s-style envelope, and all of them declare
kindon the request schema:Write tools that accept a manifest (
deploy_api,apply_config) parse this envelope and route onkind. Tools that operate on an existing resource by identity takekindas an explicit argument, normalised before use —rest_api,REST-APIandRestApiall resolve to one canonical value, since models are inconsistent about casing and separators.The spec defines six kinds:
RestApi,Mcp,LlmProviderTemplate,LlmProvider,LlmProxy,Secret.Secret,Certificate,SubscriptionPlanandSubscriptionare out of scope.Tool Reference
API lifecycle - routable kinds
deploy_apiyaml,id?createRestAPI,updateRestAPI,createMCPProxy,updateMCPProxy,createLLMProxy,updateLLMProxy,createLLMProvider,updateLLMProviderundeploy_apikind,id,confirmdeleteRestAPI,deleteMCPProxy,deleteLLMProxy,deleteLLMProviderdeploy_apiomitsidto create and passes it to update. Whenidis present it must agree withmetadata.name, so a mistyped id cannot overwrite the wrong resource.Config lifecycle - non-routable kinds
apply_configyaml,id?createLLMProviderTemplate,updateLLMProviderTemplatedelete_configkind,id,confirmdeleteLLMProviderTemplateSupporting configuration is referenced by routable resources but takes no traffic itself. Certificate upload/reload is intended for this pair once certificates come into scope.
Read - all kinds
list_resourceskind?listRestAPIs,listMCPProxies,listLLMProxies,listLLMProviders,listLLMProviderTemplatesget_resourcekind,idgetRestAPIById,getMCPProxyById,getLLMProxyById,getLLMProviderById,getLLMProviderTemplateByIdlist_resourceswith a kind returns full manifests. Without a kind it returns a cross-kind inventory, per-kind counts plus a compact summary. So a bare call stays small enough to be a useful starting point.Credentials (need to be discussed)
issue_api_keykind,id,keyNamecreateAPIKey,createLLMProviderAPIKey,createLLMProxyAPIKeylist_api_keyskind,idlistAPIKeys,listLLMProviderAPIKeys,listLLMProxyAPIKeysrotate_api_keykind,id,keyName,metadata?regenerateAPIKey,updateAPIKey, and LLM equivalentsrevoke_api_keykind,id,keyName,confirmrevokeAPIKey,revokeLLMProviderAPIKey,revokeLLMProxyAPIKeyrotate_api_keymerges regeneration with an optional metadata update, since the two are almost always performed together.Diagnostics - admin port (needs to be discussed)
get_gateway_statusgetHealth,getXDSSyncStatusget_config_dumpsection?getConfigDumpget_gateway_statusreturns liveness plus the current policy-chain xDS version, which is what makes it useful before and after a deploy to confirm a change actually applied.sectionacceptsapis,policies,certificates,statistics, orall(default: summary).Kind Registry
Each kind is registered once with the service calls implementing its verbs.
Routabledetermines which tool pair owns its write path.RestApideploy_api/undeploy_apiRestAPIServiceMcpdeploy_api/undeploy_apiMCPDeploymentServiceLlmProxydeploy_api/undeploy_apiLLMDeploymentServiceLlmProviderdeploy_api/undeploy_apiLLMDeploymentServiceLlmProviderTemplateapply_config/delete_configLLMDeploymentServiceAdding a New Kind
Adding a resource kind requires one registry entry, one alias, and
x-mcp-toolannotations on its operations. No tool is added, renamed or modified:Tool Selection Rules
Rule 1 - One tool per intent, not per operation
Tool identity is the verb, never the noun.
create_rest_api,create_mcp_proxyandcreate_llm_proxyare one tool (deploy_api) because they are one intent. Splitting them forces the model to choose between near-identical descriptions, which is where selection errors come from.Rule 2 - The kind is never invented
deploy_api,apply_configundeploy_api,delete_config,list_resources,get_resource, credential toolsRule 3 - Destructive tools require explicit confirmation
undeploy_apiconfirm: truedelete_configconfirm: truerevoke_api_keyconfirm: trueA missing key, a non-boolean value, or
falseall refuse the call, so the guard is closed by default.Rule 4 - Write tools are class-scoped; read tools are not
deploy_api/undeploy_apiaccept only routable kinds.apply_config/delete_configaccept only supporting configuration.list_resources/get_resourceaccept every kind.A rejection names the tool that does own the kind - "kind
LlmProviderTemplateis a supporting configuration, not a routable resource; use apply_config or delete_config for it" - so a caller that picks wrong corrects itself without another round trip to the user.Rule 5 - Validation errors are surfaced, infrastructure errors are not
Manifest shape is not validated at the tool layer. The same service layer the REST endpoints use parses and validates every manifest, and its validation errors are returned to the caller verbatim, because that is how an assistant learns to correct a document and retry. Storage and infrastructure failures are logged with a correlation id and returned as a sterile message.
MCP Server - Auth related configs
Mode 1: No-auth (development only)
With no IdP configured the gateway already runs unauthenticated by design;
enforceScopesis false and every tool is open. This is an explicit, logged branch rather than an accidental fall-through.Mode 2: External IdP with OIDC discovery
GET /.well-known/oauth-protected-resourcereturns:{ "resource": "<external_base_url>/mcp", "authorization_servers": ["https://api.asgardeo.io/t/<org>/oauth2/token"], "scopes_supported": ["admin", "consumer", "developer"], "bearer_methods_supported": ["header"] }scopes_supportedis computed from the union of every tool's allowed scopes, so it cannot drift from what the server actually enforces.Basic auth remains available and is what the REST API already uses (
controller.auth.basic, bcrypt-hashed passwords, local roles). It is appropriate for local development only: credentials travel on every request, there is no expiry or scoped access, revocation needs a config change plus restart, and it is not referenced by RFC 9728.Static bearer tokens were considered and rejected - no built-in expiry, manual rotation and out-of-band distribution, and no identity or role claims unless embedded in a signed JWT, at which point it is a simplified OIDC reimplementation.
Open Decisions
x-basicauth-roles-admin,developer,consumer. Adopting it meansx-basicauth-rolesis no longer the right source and a parallelx-required-scopesis needed.9090, admin9094/9092). Single server with per-tool routing, or separate endpoints?All reactions