Repository navigation
[Proposal] Subscription support for MCP proxies #3803
AqeelMuhammad
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.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Extend the API subscription model introduced in #1315 (plans, the
subscription-validationpolicy, subscription tokens) to MCP proxies.Subscription-Key.Tracking issue: #3787. Feedback welcome on the design and on the review questions at the end.
Problem Statement
subscription-validationis attached to an MCP proxy, every call returns 403, even with a valid subscription token, because the gateway only loads subscriptions for REST APIs.Who are we solving the problem for?
Why should it be solved?
Use Cases
Platform API (control plane)
gold= 10,000 requests/hour andfive-per-minute= 5 requests/minute (/subscription-plans; exists today).subscriptionPlans).subscription-validation).API Portal / MCP Hub
Subscription-Key(plusAPI-Key/ OAuth2 if the MCP proxy also requires authentication).AI Gateway (runtime)
ratelimit-*/retry-afterheaders.Proposed Solution
Overview
flowchart TB DEV["MCP proxy developer / org admin<br/>(Platform API REST · CLI · CI)"] ADM["Portal admin"] PA[("Platform API<br/>subscription_plans · mcp_proxies (+subscriptionPlans)<br/>subscriptions · events")] subgraph PORTAL["API Portal / MCP Hub"] HUB["MCP server page<br/>plans · Subscribe · token · API key"] end subgraph GW["AI Gateway"] direction LR GC[("gateway-controller<br/>plans · MCP spec · subscriptions")] PE["policy engine<br/>subscription-validation"] EN["Envoy"] end C["MCP client / agent"] UP["Upstream MCP server"] DEV -->|"plans · MCP proxy + subscriptionPlans · deploy"| PA ADM -->|"① add MCP server<br/>(mcp.yaml: referenceId + plans)"| HUB HUB -->|"② webhook subscription.* / apikey.*"| PA PA ==>|"③ WebSocket events<br/>mcpproxy.deployed · subscriptionPlan.* · subscription.*"| GC GC -.->|"REST pull on (re)connect"| PA GC -->|"xDS routes"| EN GC -->|"xDS PolicyChain + SubscriptionState"| PE C -->|"④ POST /ctx/mcp<br/>Subscription-Key (+ API-Key)"| EN EN <-->|ext_proc| PE EN --> UPUser flow
flowchart TD A["① Org admin<br/>creates plans<br/>POST /subscription-plans (exists today)"] --> B["② MCP proxy developer<br/>sets subscriptionPlans + subscription-validation<br/>PUT /mcp-proxies/{id}"] B --> C["③ Deploy to gateway<br/>proxy + policy + plans"] C --> D["④ Add to API Portal / MCP Hub<br/>mcp.yaml with referenceId + plans"] D --> E["⑤ Consumer (MCP Hub)<br/>pick a plan → Subscribe → token"] E --> F["⑥ Configure MCP client<br/>Subscription-Key (+ API-Key)"] F --> G{"⑦ Call the MCP server"} G -->|valid, within quota| H["200 · tools work"] G -->|no / invalid / suspended token| I["403"] G -->|quota used up| J["429 + ratelimit-* headers"] E --> K["⑧ Manage: change plan · regenerate token · unsubscribe"]1. Org admin defines plans (no change):
POST /api/v0.9/subscription-plans.2. MCP proxy developer enables plans and requires a subscription. Set
subscriptionPlansand attachsubscription-validationon the MCP proxy:3. Deploy.
POST /api/v0.9/mcp-proxies/openai-docs-mcp/deployments {"name": "v1.0", "base": "current", "gatewayId": "my-gateway"}. The proxy, its policy and its enabled plans go to the gateway; existing subscriptions are pushed with the deployment.4. Add to the API Portal / MCP Hub. Until publishing from Platform API is available (a separate feature), the portal admin adds the MCP server with
mcp.yaml(spec.referenceId: openai-docs-mcp,spec.subscriptionPlans: [Gold, FivePerMinute]).5. Consumer subscribes in the MCP Hub. The MCP server page shows "Subscription required" and the plans.
6. Consumer gets the token and a ready MCP client config (
Subscription-Key, andAPI-Keyif the proxy also requires one).7. Consumer calls the MCP server with the headers: 200 within quota, 429 when the plan's quota is used up, 403 without a valid subscription.
8. Consumer manages subscriptions: change plan, regenerate token, unsubscribe. APIs and MCP servers are listed together.
If the MCP proxy has no authentication policy
Invoke with the subscription token only:
If the MCP proxy also has
api-key-auth/ OAuth2Invoke with both credentials, as for REST in #1315:
Implementation
1. Platform API
a)
subscriptionPlanson MCP proxies. Same field and rules asRESTAPI.subscriptionPlans:configurationJSON, as REST does. No schema change (see DB changes).GET /mcp-proxies/{id}.b) Deployment artifact. The MCP deployment YAML sent to the gateway carries the plans:
c) Subscriptions for MCP proxies. The existing API with
kind: Mcp:SUBSCRIPTION_PLAN_NOT_ENABLEDotherwise (for every subscribable kind).PUT /subscriptions/{id}(status, exists; plan change, NEW, with the same plan check),POST /subscriptions/{id}/regenerate-token(NEW),DELETE(exists).GET /subscriptions?artifactId=<mcp handle>lists an MCP proxy's subscriptions (operator view).d) Events to gateways (existing WebSocket events; payloads unchanged):
subscription.created|updated|deletedfor MCP subscriptions, sent to gateways where the MCP proxy is deployed.e) Gateway sync endpoint. The gateway's reconnect sync must be able to fetch MCP subscriptions: make
GET /api/internal/v1/apis/{id}/subscriptionskind-agnostic (artifact UUID), or add/api/internal/v1/mcp-proxies/{id}/subscriptions.f) API Portal webhooks.
subscription.*/apikey.*events withdata.api.type = "Mcp"resolve the MCP proxy byref_id. The plan check from (c) applies.2. AI Gateway
a) MCP spec:
MCPProxyConfigData.subscriptionPlansin the management OpenAPI; stored with the MCP config.b) Gateway controller:
SubscriptionState, using a set of subscribable kinds (RestApi,Mcp;GraphQLApias a follow-up). Map the subscription's API id to the MCP config, including theCPArtifactIDmapping for gateway-originated proxies.c) Policy (
subscription-validation) on MCP routes (headers-only, works with SSE responses):403 {"error":"forbidden","message":"Subscription required for this API"}429+ratelimit-limit,ratelimit-remaining,ratelimit-reset,ratelimit-policy,retry-afterSubscription-Keystripped before the upstreamMINUTE,HOUR,DAY,MONTH) are normalised to what the policy expects (see [Bug]: Per-minute subscription plan limits are never enforced at the gateway (MINUTE is not understood by subscription-validation) #3654).tools/callshould count, a policy param, e.g.countMethods: ["tools/call"], reads the JSON-RPC method.3. API Portal / MCP Hub
DB changes
No new tables or columns are required.
mcp_proxies.configuration(JSON) gainssubscriptionPlans, the same way REST APIs store it inrest_apis.configuration.subscriptionsis already artifact-generic (artifact_uuid,subscription_plan_uuid, token encrypted + hash).subscriptionPlans.subscriptions(api_id,subscription_token_hash,subscription_plan_id) andsubscription_plansare already artifact-generic.api_metadatarows (typeMcp);api_subscription_plan_mappingsandsubscriptionsalready work for them.Sequence diagrams
Configure and deploy
sequenceDiagram autonumber actor Dev as MCP proxy developer participant PA as Platform API participant GC as Gateway controller participant PE as Policy engine Dev->>PA: PUT /mcp-proxies/{id} {subscriptionPlans:[gold, five-per-minute], policies:[subscription-validation]} PA->>PA: validate plans (exist, ACTIVE) → store in configuration Dev->>PA: POST /mcp-proxies/{id}/deployments {name, base, gatewayId} PA-->>GC: WS mcpproxy.deployed GC->>PA: GET /internal/v1/mcp-proxies/{id} (spec incl. subscriptionPlans) GC->>GC: store spec · routes · policy chain PA-->>GC: WS subscription.created × N (backfill existing subscriptions) GC->>PE: xDS PolicyChain + SubscriptionState (MCP entries) GC-->>PA: deployment.ack → DEPLOYEDSubscribe from the MCP Hub
sequenceDiagram autonumber actor U as Consumer participant HUB as API Portal / MCP Hub participant PA as Platform API participant GC as Gateway controller participant PE as Policy engine U->>HUB: Subscribe (plan Gold) HUB->>HUB: check plan is offered · create subscription + token · outbox event HUB-->>U: 201 + subscription token HUB-)PA: webhook subscription.created {api.ref_id, type: Mcp, plan.ref_id, token(enc)} PA->>PA: verify signature · resolve MCP proxy + plan · plan ∈ subscriptionPlans? · store PA-)GC: WS subscription.created (only gateways with the proxy deployed) GC->>GC: store (token hash) · rebuild SubscriptionState incl. Mcp GC->>PE: xDS SubscriptionState {apiId, tokenHash, status, limit, unit}Invoke
sequenceDiagram autonumber participant C as MCP client participant EN as Envoy participant PE as Policy engine (subscription-validation) participant UP as Upstream MCP server C->>EN: POST /openai-docs-mcp/mcp initialize · Subscription-Key: T EN->>PE: ext_proc request headers alt no / unknown / inactive token PE-->>C: 403 {"error":"forbidden","message":"Subscription required for this API"} else count(apiId, T) > plan limit in window PE-->>C: 429 + ratelimit-limit / ratelimit-remaining / retry-after else valid PE->>EN: continue (strip Subscription-Key, count +1) EN->>UP: initialize UP-->>C: 200 (JSON or SSE) end Note over C,PE: tools/list and tools/call repeat the same check<br/>(what counts toward the quota: open question)Spec changes (summary)
openapi.yamlMCPProxy.subscriptionPlans; new errorSUBSCRIPTION_PLAN_NOT_ENABLEDon subscription create/plan change; plan change +regenerate-tokenon/subscriptions/{id}/apis/{id}/subscriptionsor/mcp-proxies/{id}/subscriptions)kind: Mcp)spec.subscriptionPlansMCPProxyConfigData.subscriptionPlans; subscriptions endpoints accept MCP proxiessubscription-validationpolicy definitioncountMethodsparam; unit normalisationmcp.yamlspec.referenceId,spec.subscriptionPlansalready supported)subscription.*sent for MCP artifactsBackward compatibility
subscriptionPlansis optional. MCP proxies without it behave as today.Testing
tests/framework): a "secured MCP invocation" feature mirroring the REST one:initialize/tools/list/tools/call200;Subscription-Key; REST subscription regression.Documentation
Review questions
subscription-validationfrom the AI Workspace policy editor, should it warn when the proxy has no plans, or require them?initialize,notifications/initialized,tools/list,tools/call; ~3 units before the first tool call), onlytools/call, or configurable per proxy?subscription-validationpolicy, or attach it automatically when plans are set?GET /api/internal/v1/apis/{apiId}/subscriptions. Options:apiRepo.GetAPIByUUID). The deployment check (deployment_statusby artifact UUID), the subscription listing, and the response (GatewaySubscriptionInfowithapiId) are already generic.RestApi,Mcp, laterGraphQLApi)./apis/path now means "any artifact".GET /api/internal/v1/mcp-proxies/{proxyId}/subscriptions, with the same response format./mcp-proxies/{proxyId}) and doesn't touch the REST path.All reactions