5.14 Billing (read-only)
Parent: Story 5 (#7232)
Tool file: new forge/ee/lib/mcp/tools/billing.js
Summary
Read-only billing/subscription visibility for a team: billing info and a customer-portal link. Write operations (checkout, manual billing, trial) are either interactive (Stripe redirect) or admin-only and are excluded.
Read tools (phase 1)
readOnlyHint: true.
| Tool |
Endpoint |
Scope |
Annotation |
platform_get_team_billing |
GET /ee/billing/teams/:teamId |
team:edit |
read |
platform_get_team_customer_portal |
GET /ee/billing/teams/:teamId/customer-portal |
team:edit |
read (returns a Stripe portal URL) |
Design notes:
- Billing routes are mounted only when the platform is configured with billing (
app.config.billing); there is no per-team getFeatureProperty gate on these routes.
GET /ee/billing/teams/:teamId returns 404 for no active subscription and 403 billing_unmanaged for unmanaged subscriptions; surface these descriptively.
- Both read tools use
team:edit (owner-level) even though they are reads; annotate readOnlyHint: true and rely on the route's owner check.
customer-portal issues an HTTP 303 redirect to the Stripe portal (it does not return the URL in a JSON body); the MCP tool must capture the Location header and return that URL to the agent, not parse a response body.
Scopes to allow-list (IMPLICIT_TOKEN_SCOPES['user:expert-mcp']) and access-tag (#7445):
team:edit is already write-tagged (owner-level); these are read tools by annotation. No new read-scope needed; ensure the MCP annotation is readOnlyHint: true so read-only PATs can call them (route still enforces owner role).
Tool definitions (description + zod inputSchema):
platform_get_team_billing: {
description: 'Read a team billing and subscription details. Returns 404 for no active subscription and 403 billing_unmanaged for unmanaged subscriptions; surface these descriptively.',
inputSchema: z.object({ teamId: z.string().describe('Team hashid whose billing and subscription details to read') })
}
platform_get_team_customer_portal: {
description: 'Returns a URL to the team Stripe customer portal, where billing and subscription settings are managed.',
inputSchema: z.object({ teamId: z.string().describe('Team hashid to open a Stripe customer-portal session for') })
}
Tests:
- Both tools allowed for read-only PAT (annotation) but require owner role at the route.
billing_unmanaged/no-subscription states return descriptive errors.
5.14 Billing (read-only)
Parent: Story 5 (#7232)
Tool file: new
forge/ee/lib/mcp/tools/billing.jsSummary
Read-only billing/subscription visibility for a team: billing info and a customer-portal link. Write operations (checkout, manual billing, trial) are either interactive (Stripe redirect) or admin-only and are excluded.
Read tools (phase 1)
readOnlyHint: true.platform_get_team_billingGET /ee/billing/teams/:teamIdteam:editplatform_get_team_customer_portalGET /ee/billing/teams/:teamId/customer-portalteam:editDesign notes:
app.config.billing); there is no per-teamgetFeaturePropertygate on these routes.GET /ee/billing/teams/:teamIdreturns 404 for no active subscription and 403billing_unmanagedfor unmanaged subscriptions; surface these descriptively.team:edit(owner-level) even though they are reads; annotatereadOnlyHint: trueand rely on the route's owner check.customer-portalissues an HTTP 303 redirect to the Stripe portal (it does not return the URL in a JSON body); the MCP tool must capture theLocationheader and return that URL to the agent, not parse a response body.Scopes to allow-list (
IMPLICIT_TOKEN_SCOPES['user:expert-mcp']) and access-tag (#7445):team:editis already write-tagged (owner-level); these are read tools by annotation. No new read-scope needed; ensure the MCP annotation isreadOnlyHint: trueso read-only PATs can call them (route still enforces owner role).Tool definitions (description + zod inputSchema):
Tests:
billing_unmanaged/no-subscription states return descriptive errors.