From 6136fc863f74542557a3e7cd9b8010cf9d70160d Mon Sep 17 00:00:00 2001 From: imeepos Date: Tue, 14 Jul 2026 01:27:00 -0700 Subject: [PATCH] Refactor MCP server resources and creation logic - Moved the static resource creation logic to a new file `staticResources.ts` for better organization and separation of concerns. - Introduced `createConfiguredAutumnOperationsMCPServer` in `createServer.ts` to encapsulate server creation logic and configuration. - Updated `createAutumnOperationsMCPServer` in `server.ts` to utilize the new server creation function, simplifying the server instantiation process. - Removed redundant code related to resource handling from `server.ts` to streamline the server setup. --- packages/mcp/package.json | 1 + .../mcp/scripts/build-worker-resources.ts | 46 +++++++++++++++++++ packages/mcp/src/index.ts | 6 ++- .../resources-v2/generated/workerResources.ts | 39 ++++++++++++++++ packages/mcp/src/resources-v2/index.ts | 23 +--------- .../mcp/src/resources-v2/staticResources.ts | 24 ++++++++++ packages/mcp/src/server/createServer.ts | 37 +++++++++++++++ packages/mcp/src/server/server.ts | 30 ++++-------- 8 files changed, 162 insertions(+), 44 deletions(-) create mode 100644 packages/mcp/scripts/build-worker-resources.ts create mode 100644 packages/mcp/src/resources-v2/generated/workerResources.ts create mode 100644 packages/mcp/src/resources-v2/staticResources.ts create mode 100644 packages/mcp/src/server/createServer.ts diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 0cffccb4c..09325f646 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -13,6 +13,7 @@ ], "scripts": { "build": "tsc", + "build:worker-resources": "bun scripts/build-worker-resources.ts", "ts": "tsc --noEmit", "test": "bun test tests/unit", "test:eval": "ENV_FILE=.env infisical run --env=dev --recursive -- bun test tests/evals", diff --git a/packages/mcp/scripts/build-worker-resources.ts b/packages/mcp/scripts/build-worker-resources.ts new file mode 100644 index 000000000..563fab7a3 --- /dev/null +++ b/packages/mcp/scripts/build-worker-resources.ts @@ -0,0 +1,46 @@ +import { mkdirSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + autumnMcpResourceUris, + autumnMcpResources, +} from "../src/resources-v2/index.js"; +import { autumnMcpInstructions } from "../src/resources-v2/mcpInstructions.js"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const outputPath = resolve( + scriptDir, + "../src/resources-v2/generated/workerResources.ts", +); + +const resourceUris = autumnMcpResourceUris(); +const resourceDocs = await Promise.all( + resourceUris.map(async (uri) => { + const content = await autumnMcpResources.getResourceContent({ uri }); + const listed = (await autumnMcpResources.listResources()).find( + (resource) => resource.uri === uri, + ); + if (!listed) { + throw new Error(`Generated resource list did not include ${uri}`); + } + return { + name: listed.name, + title: listed.title ?? listed.name, + description: listed.description ?? "", + priority: listed.annotations?.priority ?? 0.8, + audience: listed.annotations?.audience ?? ["assistant"], + uri, + text: content.text, + }; + }), +); + +const source = `import type { AutumnMcpResourceDoc } from "../../resources/types.js"; + +export const workerAutumnMcpInstructions = ${JSON.stringify(autumnMcpInstructions)} as const; + +export const workerAutumnMcpResourceDocs = ${JSON.stringify(resourceDocs, null, "\t")} satisfies AutumnMcpResourceDoc[]; +`; + +mkdirSync(dirname(outputPath), { recursive: true }); +writeFileSync(outputPath, source); diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index 98971a6b6..e82d3061d 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -25,6 +25,10 @@ export { type OAuthEnvironment, } from "./server/auth/auth.js"; export type { MCPServerFlags } from "./server/flags.js"; +export { + createConfiguredAutumnOperationsMCPServer, + type AutumnMcpServerOptions as ConfiguredAutumnMcpServerOptions, +} from "./server/createServer.js"; export { type AutumnMcpServerOptions, createAutumnOperationsMCPServer, @@ -36,4 +40,4 @@ export { getAutumnMcpRuntimeConfig, setAutumnMcpRuntimeConfig, } from "./server/runtime.js"; -export { createStaticAutumnMcpResources } from "./resources-v2/index.js"; +export { createStaticAutumnMcpResources } from "./resources-v2/staticResources.js"; diff --git a/packages/mcp/src/resources-v2/generated/workerResources.ts b/packages/mcp/src/resources-v2/generated/workerResources.ts new file mode 100644 index 000000000..15268732a --- /dev/null +++ b/packages/mcp/src/resources-v2/generated/workerResources.ts @@ -0,0 +1,39 @@ +import type { AutumnMcpResourceDoc } from "../../resources/types.js"; + +export const workerAutumnMcpInstructions = "# Autumn MCP Instructions\n\nUse Autumn MCP tools for Autumn customer, plan, feature, balance, schedule, and billing state.\nAlways read the relevant Autumn MCP resources to understand Autumn before starting a task.\n\n\n\n- Speak in the user's domain language first; use Autumn terms only when they help clarify implementation.\n- Translate user terms into Autumn concepts internally before using tools, but do not foreground ontology labels in user-facing replies.\n- When a mapping matters, explain the practical behavior instead of the label, e.g. what gets billed, what owns access, and what is tracked underneath it.\n- When multiple missing read-only lookups are required, such as plans and features, call them in the same tool batch.\n- Autumn tool names such as `getAgentRules` and `listPlans` refer to MCP API tools; call the tool directly, never through Bash.\n\n\n\n## Resources\n\n- For pricing setup, plan creation, plan updates, and plan modeling, follow `autumn://docs/plan-management`.\n- For billing actions such as attaching plans, updating subscriptions, canceling or uncanceling subscriptions, creating schedules, and changing customer billing state, follow `autumn://docs/billing`.\n\n## Org Rules\n\n- Call `getAgentRules` when org-specific behavior could change the action: entity defaults, credit defaults, org notes, or writes.\n- Do not call `getAgentRules` for purely conceptual questions that do not depend on the current org.\n\n## Writes\n\n- Use preview tools before billing writes.\n- Write tools are destructive; obtain approval via your client's approval mechanism before calling one.\n- If a preview fails, state the blocking reason once and stop; do not call or suggest the write tool." as const; + +export const workerAutumnMcpResourceDocs = [ + { + "name": "concepts", + "title": "Concepts", + "description": "Autumn billing ontology and object relationships.", + "priority": 0.95, + "audience": [ + "assistant" + ], + "uri": "autumn://docs/concepts", + "text": "# Concepts\n\n## Intro\n\nAutumn is a database for your application billing state: features, plans, customers, subscriptions, purchases, balances, flags, and billing controls.\nIt helps you iterate on pricing, manage credit access and usage, control overage, and keep billing behavior connected to product access.\n\n\n\n- Autumn is a layer above Stripe; Stripe still handles subscription management, invoicing, and payment processing.\n- Autumn billing management provisions and updates Stripe subscriptions, schedules, invoices, and related billing objects for you.\n\n\n\n## Object Graph\n\n```txt\nOrganization\n- features[] -> Feature\n- plans[] -> Plan\n- customers[] -> Customer\n\nConfiguration model:\nFeature\n- referenced by -> Plan Item\n\nPlan\n- items[] -> Plan Item\n - feature_id -> Feature\n - optional price -> usage_based or prepaid feature price\n- price -> base recurring or one-off price\n\nPlan Item\n- feature_id -> Feature\n- optional price -> usage_based or prepaid feature price\n\nRuntime model:\nCustomer\n- subscriptions[] -> Subscription -> Plan\n- purchases[] -> Purchase -> Plan\n- balances[feature_id] -> Balance -> Feature\n- flags[feature_id] -> Flag -> Feature\n- billing_controls -> customer-level usage controls\n- entities[] -> Entity -> same runtime shape scoped under Customer\n\nEntity\n- belongs to -> Customer\n- subscriptions[] -> Subscription -> Plan\n- purchases[] -> Purchase -> Plan\n- balances[feature_id] -> Balance -> Feature\n- flags[feature_id] -> Flag -> Feature\n- billing_controls -> entity-level controls where supported\n\nFrom config to customer state:\nPlan + Customer --billing.attach--> Subscription or Purchase\nPlan + Customer + entity_id --billing.attach--> Entity-scoped Subscription or Purchase\nSubscription/Purchase -> Balance or Flag provisioning\n```\n\n### Feature\n\n\n\n- Feature is the atomic thing Autumn gates, tracks, or bills.\n- `id` is used in plan items, check/track calls, balances, and flags.\n- `name` and optional `display` labels are for dashboard and billing UI.\n\n\n\n\n\n- `Plan Item -> Feature`: defines how a plan grants or bills the feature.\n- `Balance -> Feature`: runtime state for metered features.\n- `Flag -> Feature`: runtime access for boolean features.\n\n\n\n\n\n- `boolean`: on/off access, exposed as flags.\n- `metered`, `consumable: true`: usage is spent and can reset, e.g. API calls or AI messages.\n- `metered`, `consumable: false`: persistent quantity, e.g. seats or storage.\n- `credit_system`: user-defined currency with credit costs for metered consumable features.\n- `ai_credit_system`: a monetary (dollar) balance for AI/LLM token usage, priced from Models.dev model pricing + a configured markup.\n\n\n\n\n\n- Classic `credit_system`: one shared balance for several metered features; `credit_schema` maps each `metered_feature_id` to a `credit_cost`. Track via the underlying `feature_id`.\n- `ai_credit_system`: a monetary balance (units = dollars) for AI/LLM token usage; no `credit_schema`. Cost = Models.dev model pricing + markup.\n - Markups, low to high priority: `default_markup` (global %), `provider_markups` (keyed by the model id's provider prefix), `model_markups` (per model). No markup = Models.dev base cost; `-100` = free (recorded, not deducted).\n - Model ids are `provider/model` (e.g. `anthropic/claude-opus-4-5`, `openrouter/anthropic/...`, `custom/...`). Standard models auto-price from Models.dev; `custom/...` models must set `input_cost`/`output_cost` ($/M tokens) and bill input/output only.\n - Track usage with `trackTokens` (modelId + token counts); Autumn converts to dollars and deducts.\n\n\n\n\n\n- Do not create duplicate features for the same resource; use Plan Items to vary allowance, interval, package, or price.\n- Example: `tokens` should be one feature, not separate `monthly_tokens` and `one_time_tokens` features.\n\n\n\n\n\n- `event_names`: optional aliases so one `track` request can target usage for multiple features.\n- `credit_schema`: classic `credit_system` only (not `ai_credit_system`); maps `metered_feature_id` to `credit_cost`.\n- `archived`: deprecated config; may still exist in grandfathered plans or subscriptions.\n- Legacy pricing-agent wording: `single_use` means metered consumable; `continuous_use` means metered non-consumable.\n\n\n\n\n\n- Concepts overview: https://docs.useautumn.com/documentation/concepts/overview\n- Features concept: https://docs.useautumn.com/documentation/concepts/features\n- Credit systems: https://docs.useautumn.com/documentation/modelling-pricing/credit-systems\n\n\n\n### Plan\n\n\n\n- Plan is the attachable package: Free, Pro, Enterprise, Credit Pack, Add-on, etc.\n- A plan answers two questions: what should this customer get, and how should Autumn treat it when attached?\n- Most \"what they get\" detail lives in `items[]`; most lifecycle behavior lives on plan-level fields.\n\n\n\n\n\n- `Plan -> Plan Item`: a plan has many items; items define feature grants, limits, prepaid packages, and overage prices.\n- `Subscription -> Plan`: recurring or free plan attached to a customer or entity.\n- `Purchase -> Plan`: one-off plan attached to a customer or entity.\n- `Customer/Entity + Plan --billing.attach--> Subscription/Purchase`: attach turns plan configuration into customer state.\n\n\n\n\n\n- Use `price` for the plan-level/base charge, such as $20/month for Pro or a one-off flat fee.\n- Use `items[]` as the packaging of the plan: feature grants, seats, overages, prepaid packs, boolean access, and add-on contents.\n- Common pattern: `Plan.price` is the platform/package fee; `Plan.items[]` define the packaged value and any feature-level billing.\n- `price: null` does not always mean free; the plan can still be paid if its items contain usage-based or prepaid prices.\n- If the pricing question is \"what does this feature grant or bill?\", answer it in Plan Item, not Plan.\n\n\n\n\n\n- Recurring plan: has at least one recurring paid price or recurring lifecycle; attach creates a subscription.\n- Free plan: has no paid prices; attach creates a free subscription.\n- One-off plan: has at least one paid price and all paid prices are one-off; attach creates a purchase.\n- One-off examples: $10 flat purchase, or $10 for 100 prepaid credits.\n- If any price is monthly or yearly, e.g. $10/month, it is not a one-off plan.\n\n\n\n\n\n- `auto_enable` automatically attaches the plan when a subject is created.\n- Use it for free/default access, not normal paid plans.\n- Common examples: free tier, limited-time trial access plan, entity default tier.\n- If multiple defaults exist across groups, Autumn can assign one default per group.\n- Never use `auto_enable: true` for paid plans; `Plan.price` must be null and plan items should not contain paid prepaid or usage-based prices.\n\n\n\n\n\n- Today, Autumn has no concept of \"variants\"; each variant is its own plan, e.g. `pro_monthly` or `pro_annual`.\n- Annual plan pricing can coexist with shorter plan item reset intervals, e.g. annual base price with monthly credit resets.\n\n\n\n\n\n- This covers how to MODEL trials in the catalog. For how to put a customer on a trial at attach time (card-required, no-card, revert), see the Trials concept.\n- For card-required trials, put `free_trial` on the real paid plan.\n- For no-card trials, prefer a separate limited-time trial plan, e.g. `pro_trial`, plus the real paid `pro` — it gives temporary access, expires automatically, and lets the user later enter the normal checkout flow for `pro`.\n\n\n\n\n\n- By default, attaching a plan replaces the customer's current plan in the same group.\n- Use `group` when customers can have one active plan from each independent product line.\n- Example: one `support` plan and one `sales` plan can coexist, but two `support` plans should transition.\n- Groups are not needed for simple pricing with one main subscription line.\n\n\n\n\n\n- `add_on` makes the plan additive instead of a replacement.\n- Use add-ons for top-up packs, feature packs, extra concurrency, extra storage, or recurring bolt-ons.\n- Add-ons can be attached alongside other add-ons; repeated attachment can be useful for top-ups or stacked purchases.\n- Add-ons do not participate in normal upgrade/downgrade transitions.\n\n\n\n\n\n- Concepts overview: https://docs.useautumn.com/documentation/concepts/overview\n- Plans concept: https://docs.useautumn.com/documentation/concepts/plans\n- Free plans: https://docs.useautumn.com/documentation/modelling-pricing/free-plans\n- Recurring plans: https://docs.useautumn.com/documentation/modelling-pricing/recurring\n- Trials: https://docs.useautumn.com/documentation/modelling-pricing/trials\n- Add-ons: https://docs.useautumn.com/documentation/modelling-pricing/add-ons\n\n\n\n### Plan Item\n\n\n\n- Plan Item is the join between a Plan and a Feature.\n- It defines what the customer gets for that feature, and whether usage or quantity is billed.\n- Plan items turn a Feature into a customer-facing allowance, limit, prepaid package, or overage price.\n\n\n\n\n\n- `Plan -> Plan Item`: a plan has many items.\n- `Plan Item -> Feature`: `feature_id` identifies the feature being granted or billed.\n- `Subscription/Purchase -> Balance`: metered plan items become runtime balances when attached.\n- `Subscription/Purchase -> Flag`: boolean plan items become runtime flags when attached.\n\n\n\n\n\n \n\n - Free allowance that comes with the plan.\n - For consumable features, `reset` controls the cycle, e.g. 5k credits/month on Pro.\n - `unlimited` means the feature is available without a tracked limit.\n - (note: for `ai_credit_system` items, `included` and the balance are in dollars (`included: 10` = $10))\n\n \n\n \n\n - Pass only `feature_id`; set neither `included` nor `unlimited`. `feature_id` alone grants access.\n - Grants access rather than quantity.\n - Boolean plan items cannot be paid today; charge through `Plan.price` or another metered feature instead.\n\n \n\n \n\n \n\n - Customer buys or subscribes to a consumable quantity upfront, commonly credits.\n - Use for selectable monthly buckets, volume-priced buckets, one-off credit packs, and auto top-up purchase prices.\n - Selectable monthly bucket example: $10 per 1k credits/month, customer chooses 5k credits for $50/month.\n - Volume-priced bucket example: customer selects a monthly credit bucket whose quantity maps to a flat tier price.\n - One-off credit pack example: $10 per 1k lifetime credits.\n - Auto top-up example: same one-off prepaid item is purchased automatically when customer balance falls below threshold.\n - The purchased quantity becomes prepaid balance and is drawn down as usage is tracked.\n\n \n\n \n\n - Customer commits to a persistent quantity upfront, commonly seats or static limits.\n - Quantity does not reset each cycle.\n - The committed quantity is still charged every billing cycle.\n - Can be used as a value the app reads and gates against, even if usage is not tracked.\n - Example: concurrency limit of 10, where the app checks the allowed value but does not track consumption.\n - Mid-cycle quantity changes can create prorated charges or credits.\n\n \n\n \n\n \n\n - Customer is billed in arrears for usage beyond included units.\n - Common for overage, e.g. $0.01/credit after included credits are exhausted.\n - Can be tiered, e.g. 1k API calls free, then $0.02/call up to 5k, then $0.01/call after that.\n\n \n\n \n\n - Customer is billed in arrears for measured persistent usage.\n - Common for storage or compute capacity tracked through the cycle, e.g. $0.05/GB-month for storage used.\n - Usage does not reset like consumable balance, but the billing calculation happens each cycle.\n - If the quantity is only a static entitlement like concurrency, do not use usage-based pricing unless the app reports measured usage.\n\n \n\n\n\n \n\n - `price.tiers` set per-bracket pricing; `tier_behavior` is `volume` or graduated (the default).\n - Volume (`tier_behavior: \"volume\"`): tiers are `{ amount: 0, to, flat_amount }`. `to` is the cumulative TOTAL the customer gets at that tier — it includes the item's `included` free amount, not just the paid amount. The whole selected bucket is billed the tier's `flat_amount`.\n - Graduated/per-unit: the customer pays `amount` per `billing_units` within each bracket, and `included` free units are added on top of what they buy.\n\n \n\n\n\n- A single item can combine included units with paid usage, e.g. 5k credits/month then $0.01/credit.\n- A single item can combine included units with prepaid quantity, e.g. 3 seats included then $10/seat prepaid.\n- The same feature can appear in multiple items when the items differ by reset interval or billing method.\n- Monthly allowance plus one-off prepaid top-up item is common for auto top-ups.\n- Prepaid monthly credit bucket plus usage-based overage item is valid when the same feature needs both selected quantity and overage pricing.\n- For per-unit pricing with a base subscription fee, use `Plan.price` for the base fee and a Plan Item for the per-unit feature price.\n\n\n\n\n\n- Included monthly credits:\n ```json\n { \"feature_id\": \"AI_CREDITS\", \"included\": 5000, \"reset\": { \"interval\": \"month\" }, \"price\": null }\n ```\n- Usage-based overage after included credits:\n ```json\n { \"feature_id\": \"AI_CREDITS\", \"included\": 5000, \"reset\": { \"interval\": \"month\" }, \"price\": { \"amount\": 0.01, \"interval\": \"month\", \"billing_units\": 1, \"billing_method\": \"usage_based\" } }\n ```\n- Tiered usage-based API calls:\n ```json\n { \"feature_id\": \"api_calls\", \"included\": 1000, \"reset\": { \"interval\": \"month\" }, \"price\": { \"tiers\": [{ \"to\": 5000, \"amount\": 0.02 }, { \"to\": \"inf\", \"amount\": 0.01 }], \"interval\": \"month\", \"billing_units\": 1, \"billing_method\": \"usage_based\" } }\n ```\n Customer gets 1k calls free, then pays tiered overage at the end of the cycle.\n- Base fee plus per-unit seats:\n ```json\n { \"plan_price\": { \"amount\": 10, \"interval\": \"month\" }, \"item\": { \"feature_id\": \"seats\", \"included\": 1, \"reset\": null, \"price\": { \"amount\": 10, \"interval\": \"month\", \"billing_units\": 1, \"billing_method\": \"usage_based\" } } }\n ```\n Creates $10/month base price with 1 included seat, then $10 per additional seat.\n- Prepaid selectable monthly credit bucket:\n ```json\n { \"feature_id\": \"AI_CREDITS\", \"included\": 5000, \"reset\": { \"interval\": \"month\" }, \"price\": { \"amount\": 10, \"interval\": \"month\", \"billing_units\": 1000, \"billing_method\": \"prepaid\" } }\n ```\n The customer passes `feature_quantities` to choose total monthly credits; quantity includes included units.\n- Prepaid volume-priced bucket:\n ```json\n { \"feature_id\": \"AI_CREDITS\", \"included\": 5000, \"reset\": { \"interval\": \"month\" }, \"price\": { \"tiers\": [{ \"to\": 31000, \"amount\": 0, \"flat_amount\": 250 }, { \"to\": \"inf\", \"amount\": 0, \"flat_amount\": 10000 }], \"tier_behavior\": \"volume\", \"interval\": \"month\", \"billing_units\": 1, \"billing_method\": \"prepaid\" } }\n ```\n Use when the user selects a recurring bucket size and the bucket maps to a flat monthly price.\n- One-off prepaid top-up item:\n ```json\n { \"feature_id\": \"AI_CREDITS\", \"included\": 0, \"reset\": null, \"price\": { \"amount\": 10, \"interval\": \"one_off\", \"billing_units\": 1000, \"billing_method\": \"prepaid\" } }\n ```\n Use for credit packs and auto top-ups; auto top-up threshold and quantity live on customer billing controls.\n- Prepaid seats:\n ```json\n { \"feature_id\": \"seats\", \"included\": 5, \"reset\": null, \"price\": { \"amount\": 10, \"interval\": \"month\", \"billing_units\": 1, \"billing_method\": \"prepaid\" } }\n ```\n Use when the customer commits to a seat quantity upfront; mid-cycle quantity changes can prorate.\n- Usage-based storage:\n ```json\n { \"feature_id\": \"storage_gb\", \"included\": 100, \"reset\": null, \"price\": { \"amount\": 0.05, \"interval\": \"month\", \"billing_units\": 1, \"billing_method\": \"usage_based\" } }\n ```\n Use when persistent usage is measured through the cycle and invoiced in arrears.\n\n\n\n\n\n- `rollover`: for consumable features with reset intervals; unused balance can carry forward subject to cap and expiry rules.\n- For paid consumable items, `price.interval` determines both the billing cycle and the reset cycle.\n- `proration`: mainly relevant to prepaid quantity changes, especially non-consumable or seat-like items.\n- `max_purchase`: less common cap on purchasable units; customer billing controls are often used for spend or purchase limits.\n- `entity_feature_id`: legacy/deprecated per-entity balance scoping; prefer entity-scoped plan attachments.\n- Auto top-ups require a one-off prepaid item for the feature; customer billing controls configure threshold and quantity.\n\n\n\n\n\n- Concepts overview: https://docs.useautumn.com/documentation/concepts/overview\n- Plan items concept: https://docs.useautumn.com/documentation/concepts/plan-items\n- Prepaid pricing: https://docs.useautumn.com/documentation/modelling-pricing/prepaid-pricing\n- Usage-based pricing: https://docs.useautumn.com/documentation/modelling-pricing/usage-based-pricing\n- One-off purchases: https://docs.useautumn.com/documentation/modelling-pricing/one-off-purchases\n- Auto top-ups: https://docs.useautumn.com/documentation/modelling-pricing/auto-top-ups\n- Rollovers: https://docs.useautumn.com/documentation/modelling-pricing/rollovers\n- Volume-based tiers: https://docs.useautumn.com/documentation/modelling-pricing/volume-based-tiers\n- Per-unit pricing: https://docs.useautumn.com/documentation/modelling-pricing/per-unit-pricing\n\n\n\n### Trials\n\n\n\n- A trial gives a customer temporary access to a plan before billing begins.\n- Set a trial with `free_trial` on attach: `{ duration_length, duration_type (day|month|year), card_required, on_end }`.\n- `on_end`: `bill` charges when the trial ends (default); `revert` expires the trial and restores the customer's previous plan.\n\n\n\n\n\n- The customer is on no paid plan. Three flows:\n- Card-required trial (preferred, default): attach with `free_trial` and `card_required: true`. If the customer has no payment method, the attach returns a checkout URL (or an invoice URL when `invoice_mode.enabled`) to collect a card; they are charged when the trial ends.\n- No-card trial: attach with `card_required: false`. The subscription starts with no card and ends at trial end if none is added. While on it, the customer cannot upgrade or attach another plan until they add a card via the Stripe billing portal.\n- Limited-time trial plan: a separate free, no-card plan in the catalog (e.g. `pro_trial`) that grants temporary access, expires automatically, then routes the customer into the normal checkout for the real plan. See `` in the Plan concept for modeling. Some orgs configure this — recognize and use it when present.\n- Default to `card_required: true` unless the user explicitly asks for no card.\n\n\n\n\n\n- The customer already has an active (Stripe) subscription — common in sales-led trials.\n- Regular flow: attaching a plan with a trial (or updating the subscription to add one) resets the Stripe billing anchor/cycle. This can be undesired so should be carefully treated.\n- Revert flow: attach the new plan with `on_end: \"revert\"` (and `card_required: false`). This grants the plan in Autumn without touching the Stripe subscription; at trial end Autumn moves the customer back to their original plan, preserving the existing billing cycle.\n- Set `plan_schedule: \"immediate\"` on the revert-flow attach so the trial starts now; without it a no-base-price plan over a paid sub is scheduled for end of cycle.\n\n\n\n### Customer and Entity\n\n\n\n- Customer and Entity are the runtime view of billing state: what plans are attached, what access exists, what usage has happened, and what billing controls apply.\n- A Customer is the primary subject being billed or entitled, usually a user, account, workspace, or organization.\n- Entities are optional child subjects under a customer, such as deployments, seats, users, projects, or sub-accounts.\n- Depending on the org's configuration, entities may not be used at all; if they are in play, billing state can exist at both customer and entity scope.\n- Features, Plans, and Plan Items define configuration; Customer and Entity show how that configuration materializes for one real subject.\n\n\n\n\n\n- `subscriptions[]` and `purchases[]` show which plans this customer or entity has subscribed to or bought.\n- These arrays describe the join between subject and plan: status, start/end dates, expiry, schedule state, quantity, and attached plan context.\n- `flags[feature_id]` and `balances[feature_id]` show runtime feature state for this subject.\n- Boolean features materialize as flags: access is on or off.\n- Flag fields mostly mirror the API reference and are straightforward, so this card does not expand every field.\n- Metered and credit features materialize as balances: aggregate granted, included, usage, remaining, reset timing, and related runtime details.\n\n\n \n\n - Balances are the runtime state for metered and credit-system features.\n - The parent balance object is an aggregate view for one feature.\n - Each breakdown item is the actual balance source: usually from attaching a plan item, or from a standalone grant created with `balances.create`.\n - Plan source shape: `Customer/Entity -> Balance Breakdown -> Plan Item`.\n - `granted` is included grant plus prepaid grant.\n - `remaining` is the positive balance left from included/prepaid grants and never goes below 0.\n - `usage` is how much has been used; if usage exceeds granted, the subject is in overage.\n - Other balance fields, such as reset timing and unlimited status, are usually self-explanatory from the API reference.\n\n \n\n\n\n\n\n\n\n- Customer-level state belongs to the parent customer.\n- Entity-level state belongs to one specific entity under the customer.\n- Customer-level check/track calls do not inherit entity-level subscriptions, purchases, balances, or flags.\n- Entity-level check/track calls can use customer-level state plus matching entity-level state.\n- If a feature is granted only at entity level, include `entity_id` when checking or tracking it.\n- If all entities share the same allowance, model the allowance at customer level and omit `entity_id` for shared usage.\n- Legacy `entity_feature_id` scoped one plan item's balance across many entities under the customer; this model is deprecated.\n- Prefer attaching plans at entity scope when each entity needs its own tier, balance, or subscription state.\n\n\n\n\n\n- Entity-level balances: one customer subscription grants per-entity limits, useful when all entities get the same features and limits.\n- Entity-level subscriptions: attach plans with `entity_id`, useful when each entity can have its own tier.\n- Entity-level controls can override customer-level controls for that entity where supported.\n\n\n\n\n\n- Customer `id` and entity `id` should be identifiers from the user's own app database.\n- Users do not need to store a separate Autumn-only ID for customers or entities.\n\n\n\n\n\n- Billing controls can change usage behavior after balances are provisioned; see the Billing Controls section in this Concepts resource.\n- Use `expand` to pull related details when needed, such as attached plans, features, entities, invoices, payment method, or billing-control runtime state.\n\n\n\n\n\n- Creating customers: https://docs.useautumn.com/documentation/customers/creating-customers\n- Managing customers: https://docs.useautumn.com/documentation/customers/managing-customers\n- Entities: https://docs.useautumn.com/documentation/customers/feature-entities\n- Balances concept: https://docs.useautumn.com/documentation/concepts/balances\n- Subscriptions concept: https://docs.useautumn.com/documentation/concepts/subscriptions\n- Checking access: https://docs.useautumn.com/documentation/customers/check\n- Tracking usage: https://docs.useautumn.com/documentation/customers/tracking-usage\n\n\n\n### Billing Controls\n\n\n\n- Billing controls are runtime policy on a customer or entity.\n- They do not define what a plan grants; they change how usage is allowed, capped, alerted, or topped up.\n- They are often exposed as customer-facing settings, except `overage_allowed` which is usually product/admin controlled.\n\n\n\n\n\n- `overage_allowed`: whether usage can continue after granted balance is exhausted.\n- `spend_limits`: cap overage in feature units, not dollars.\n- `usage_limits`: hard usage caps over a time window.\n - Useful when a plan grants multiple balances, e.g. 5/day and 5/month, but the customer also needs a separate 100/month cap.\n - Useful for credit systems when credits are shared, but one mapped action needs its own cap, e.g. 10 `action_1` calls/day.\n- `usage_alerts`: notify when usage crosses a threshold; alerts do not block usage.\n- `auto_topups`: automatically buy prepaid quantity when balance drops below a threshold.\n\n\n\n\n\n- Customer-level controls apply to the customer.\n- Entity-level controls can override customer-level controls for that entity.\n- Auto top-ups are customer-level only.\n\n\n\n\n\n- Inspect current customer/entity state before changing billing controls.\n- For auto top-ups, verify the feature has a one-off prepaid purchase path.\n- Do not describe alerts as blocking usage or spend limits as dollar limits unless the feature units are dollars.\n\n\n\n\n\n- Billing controls: https://docs.useautumn.com/documentation/customers/billing-controls\n- Auto top-ups: https://docs.useautumn.com/documentation/modelling-pricing/auto-top-ups\n- Spend limits and usage alerts: https://docs.useautumn.com/documentation/modelling-pricing/spend-limits\n\n" + }, + { + "name": "plan-management", + "title": "Plan Management", + "description": "How agents should help users configure, create, and update Autumn plans.", + "priority": 0.94, + "audience": [ + "assistant" + ], + "uri": "autumn://docs/plan-management", + "text": "# Plan Management\n\nUse this resource for plan-management queries: creating plans, configuring pricing, changing plan contents, modeling tiers, add-ons, trials, credit systems, prepaid quantities, or overage.\n\n\n\n- Building pricing is iterative.\n- The goal is to translate the user's intended pricing into Autumn's model: Features and Plans.\n- Ask clarifying questions and never assume behavior unless the user explicitly states it.\n- If one ambiguity changes which other questions apply, resolve it first on its own before asking the questions it governs.\n \n - Do not assume monthly credits reset with the base price interval; ask or state the needed reset interval.\n - Do not assume a paid plan item is usage-based or prepaid; ask unless the user's wording makes it explicit.\n \n\n\n\n\n\n- Follow these steps in order; finish each before the next.\n1. Overview: get the user's full pricing — every plan, what each includes, and prices. Digest it and agree how to model it in Autumn (see ).\n2. Plans + plan-level config: create/update each plan with its base price, interval, and any trial — no feature items yet.\n3. Paid features: add priced items (prepaid buckets / volume tiers + usage overage).\n4. Metered features: add the remaining metered allowances and limits.\n5. Boolean features: finalize on/off features per plan.\n- At each step: if you have enough to take a confident stab, build it and surface it for confirmation; if not, ask until you do — then move on.\n- To apply a step: `createPlan` if the plan doesn't exist yet, otherwise `updatePlan`. Before any `updatePlan`, call `hasCustomers` with the proposed plan and follow .\n\n\n\n\n\n- Versioning grandfathers existing customers on their current terms (a production concern) — e.g. raising the base price while keeping current customers on the old price.\n- `hasCustomers` returns `will_version`: true when the change differs from the live plan AND the plan has customers, so a plain `updatePlan` would create a new version.\n- Many updates should NOT version — e.g. adding or removing a boolean feature the user wants applied to all current customers. Apply those with `disable_version: true`.\n- Rule of thumb: if there are no pricing changes (neither the base price nor any plan-item price), `disable_version: true` is safe and usually right.\n- Also prefer `disable_version: true` when `will_version` is false, or in Sandbox with few customers (the user is likely still integrating, not in production).\n- When there ARE pricing changes and the plan has customers, default to versioning (omit `disable_version`) and confirm with the user first.\n\n\n\n\n\n- Help the user map their pricing onto Autumn's model; give best-practice advice.\n\n \n\n - Variants (monthly vs yearly, or two price points of the same plan) are separate plans, e.g. `pro_monthly` and `pro_annual` — Autumn has no single \"plan with variants\".\n\n \n\n \n\n - A metered allowance sold per plan (e.g. email volume, credits) is an item under each plan, not its own plan.\n - When each plan sells a different amount of that feature at a different price point, use a prepaid volume-priced item per plan: the same feature, with plan-specific `included` (free amount) and `tier_behavior: \"volume\"` tiers `{ amount: 0, to, flat_amount }`. `to` is the total quantity at that tier, including the free `included`. Add a usage-based overage item if usage beyond the top bucket should bill in arrears.\n\n ```json\n [\n {\n \"id\": \"pro\",\n \"name\": \"Pro\",\n \"price\": { \"amount\": 20, \"interval\": \"month\" },\n \"items\": [\n {\n \"feature_id\": \"credits\",\n \"included\": 1000, // 1,000 credits/month free\n \"reset\": { \"interval\": \"month\" },\n \"price\": {\n \"tiers\": [\n { \"to\": 2000, \"amount\": 0, \"flat_amount\": 200 }, // $200 for 2,000 total (1k free + 1k paid)\n { \"to\": \"inf\", \"amount\": 0, \"flat_amount\": 400 } // $400 for any amount above 2,000\n ],\n \"tier_behavior\": \"volume\",\n \"interval\": \"month\",\n \"billing_units\": 1,\n \"billing_method\": \"prepaid\"\n }\n }\n ]\n },\n {\n \"id\": \"scale\",\n \"name\": \"Scale\",\n \"price\": { \"amount\": 50, \"interval\": \"month\" },\n \"items\": [\n {\n \"feature_id\": \"credits\",\n \"included\": 5000, // 5,000 credits/month free\n \"reset\": { \"interval\": \"month\" },\n \"price\": {\n \"tiers\": [\n { \"to\": 10000, \"amount\": 0, \"flat_amount\": 300 }, // $300 for 10,000 total (5k free + 5k paid)\n { \"to\": \"inf\", \"amount\": 0, \"flat_amount\": 500 } // $500 for any amount above 10,000\n ],\n \"tier_behavior\": \"volume\",\n \"interval\": \"month\",\n \"billing_units\": 1,\n \"billing_method\": \"prepaid\"\n }\n }\n ]\n }\n ]\n ```\n\n \n\n \n\n - When several actions/endpoints draw from one shared meter (e.g. many API endpoints, or generic \"credits\"), model one `credit_system` feature the actions map into, granted once per plan; usage deducts from the shared balance.\n - Example (Exa-style): each endpoint maps 1:1 into a `requests` credit_system; a plan grants the included amount, and once exhausted, usage deducts from the shared credits balance.\n\n \n\n \n\n - For a no-card trial of a paid plan, it's usually better to create a separate free plan that mirrors the paid plan and carries the trial — a \"limited-time trial\" — rather than putting `free_trial` (`card_required: false`) on the paid plan itself. It grants temporary access, expires automatically, then routes the user into the paid plan's normal checkout.\n - See the Plan concept's `` for the reasoning, and the Trials concept for the full flow.\n\n \n\n\n\n\n\n- Use stable lowercase IDs with underscores, e.g. `pro_plan`, `chat_messages`.\n\n" + }, + { + "name": "billing", + "title": "Billing", + "description": "How agents should perform Autumn billing workflows.", + "priority": 0.94, + "audience": [ + "assistant" + ], + "uri": "autumn://docs/billing", + "text": "# Billing\n\nUse this resource for billing actions: attaching plans, updating subscriptions, creating schedules, canceling or uncanceling subscriptions, and changing customer billing state.\nRead `autumn://docs/concepts` to understand Autumn's model: Customer, Entity, Plan, Subscription, Purchase, Balance, Flag, and Billing Controls.\n\n\n\n- Map the user's billing request to one or more Autumn billing API calls.\n- Build the params for those calls before previewing.\n- Gather requirements in natural language; do not force the user to speak in Autumn field names.\n- Preview only when the params are complete enough to represent the intended billing action.\n\n\n\n\n\n- You MUST follow this checklist in order for every billing request.\n- Resolve targets with .\n- Choose the operation with .\n- Collect action-specific params with .\n- Resolve custom terms with .\n- Resolve timing with .\n- Resolve invoice, checkout, and proration behavior with .\n- If one ambiguity changes which of the other questions apply, resolve it first on its own before gathering the rest.\n- Gather all remaining missing questions from the checklist and ask them together.\n- If there are no missing questions, call the preview tool.\n- Surface the preview's immediate billing impact, then obtain approval via your client's approval mechanism.\n- If params change, update them and repeat from the relevant checklist step.\n- Once approved, apply the exact previewed billing action.\n\n\n\n\n\n- **A mutating billing action requires approval before it takes effect — obtain it via your client's approval mechanism.**\n- Don't propose or promise steps outside what your tools can do. If the goal isn't reachable, say so plainly rather than inventing a workaround.\n- Read this full resource before billing work and follow sections in order; later sections can define params that must be resolved before previewing.\n- Monetary amounts are major currency units: `$1,150` -> `1150`, not `115000`.\n- Slack billing requests usually expect immediate effect; use `plan_schedule: \"immediate\"` unless the user asks for end-of-cycle or future timing.\n- If using `invoice_mode` and the customer has no email, ask for the email and call `updateCustomer` before previewing.\n- Ask independent missing questions together in one concise message, using one bullet point per question.\n- While gathering params, ask only for values needed to build the billing request; do not explain plan internals unless the user asks.\n- If a customization is inferred, surface it for confirmation before previewing or writing. If its intent is ambiguous, ask before building — don't resolve it silently. When surfacing a customization, describe it as a patch (what was added/removed/changed vs the catalog plan), not a full restatement of every feature.\n- If the user gives an included credit/feature amount and the plan has a prepaid item for that feature, clarify whether they mean the quantity or a customization of the item, unless it's clear.\n- Before any trial action, re-read the Trials section in `autumn://docs/concepts`.\n- Adding a trial for a customer who already has a paid subscription resets the Stripe billing cycle; warn the user and offer the `on_end: \"revert\"` flow, then let them choose.\n\n\n\n\n\n- Resolve IDs from the user's message before choosing params: customer, optional entity, and plan(s).\n- If preloaded `listPlans` / `listFeatures` results are present, treat them as already-run tool results. Do not call them again unless the needed record is absent or the user asks to refresh.\n- Search remaining unknowns with the relevant lookup tools; run independent lookups in parallel.\n- If the user gives explicit IDs, verify they exist with the same lookup tools.\n- Do not create plans. If the customer or entity is missing, ask whether to create it.\n- If an explicit customer/entity/plan ID is found, move on. For plans, a perfect or very close name match is enough only if it maps to exactly one plan.\n- If sibling plans share a base name but differ by variant (e.g. `scale` vs `scale_yearly`), the name alone is not a match; ask which variant before previewing, especially for interval-specific or custom terms.\n- If an ID had to be inferred from a name or description, confirm the match with the user before moving on.\n- If the found customer has no email, remember that and ask for it before any invoice or checkout step.\n\n\n\n\n\n \n\n - Usually choose `attach` or `updateSubscription`.\n - Use `createSchedule` for multiple phases or when the request needs explicit control over future billing state.\n - Use `attach` for a single plan attach even when it has `starts_at` or `ends_at`.\n - Check the customer's existing subscriptions before choosing the action.\n\n \n\n \n\n - Use when the customer/entity is not already on the target plan: new subscription, purchase, upgrade, downgrade, add-on, or one-off top-up.\n - If they already have the target recurring plan, do not attach it again; use `updateSubscription`.\n - One-off plans are always attached because each purchase/top-up is a new purchase.\n - `attach` handles transitions: switching main plans expires the replaced plan, while add-ons are additive.\n - Autumn determines upgrade vs downgrade from normalized base and prepaid prices.\n - By default, `attach` tries to add the new plan to an existing Stripe subscription when possible; mid-cycle changes on that subscription can charge or credit prorations.\n - Downgrades default to end-of-cycle scheduling through `plan_schedule`; upgrades usually apply immediately.\n\n \n\n \n\n - Use when changing a plan the customer/entity is currently on.\n - Main uses: prepaid feature quantity changes, cancel now/end-of-cycle, uncancel, custom plan edits, version/trial changes, and discounts.\n - Use it for modifying the existing subscription state, not for moving to a different plan the customer is not on.\n\n \n\n \n\n - Use for explicit dated phases: contracts, future changes, multi-year pricing, or sequences like Pro for 6 months then Starter.\n - Treat it like replacing the customer/entity's billing configuration for that scope with the listed phase states.\n - The immediate phase previews like a transition from current state to the first phase, so prorations may apply.\n - Each phase must still be valid: do not put two non-add-on main plans from the same group in one phase.\n - Watch contracts for dates, year-by-year fees, ramps, delayed downgrades, or different packages over time.\n\n \n\n \n\n - Existing subscriptions decide `attach` vs `updateSubscription`; one-off plans are the main exception and are always attached.\n - Preview responses describe the immediate billing impact: what is charged or credited now, plus future-cycle details when present.\n - Schedules are explicit phase state. Use them when attach/update cannot express the required future configuration or level of control.\n\n \n\n\n\n\n\n- After target resolution, collect the params specific to the selected action.\n- Before previewing, resolve any required `customize` params identified in .\n- `attach`\n - If the plan has prepaid items and quantity is missing, ask for the quantity before previewing.\n - If the prepaid quantity is known, include `feature_quantities`. Undefined `feature_quantities` defaults to 0 for that feature.\n - `feature_quantities.quantity` is inclusive of the plan item's included amount. Example: if 5,000 credits are included and $10/100 credits after, passing 6,000 means only the extra 1,000 credits are charged.\n- `updateSubscription`\n - If intent is ambiguous, clarify before previewing; e.g. cancel now vs cancel at end of cycle.\n - Quantity change: include `feature_quantities`.\n - Cancel or uncancel: include `cancel_action`.\n - Custom terms: include `customize`.\n - Version, trial, or discount change: include the matching fields. (less common intent)\n- `createSchedule`\n - Build ordered `phases[]`.\n - Each phase needs timing (`starts_at` or `starting_after`) and at least one `plans[]` entry.\n- If a missing value changes billing impact, ask before previewing.\n\n\n\n\n\n- Use the `customize` object for customer-specific plan terms.\n- Base price changes go in `customize.price`; e.g. if the user says Pro is $50/month but the catalog Pro plan is $20/month, customize the price.\n- A bare number with an interval but no `# Billing\n\nUse this resource for billing actions: attaching plans, updating subscriptions, creating schedules, canceling or uncanceling subscriptions, and changing customer billing state.\nRead `autumn://docs/concepts` to understand Autumn's model: Customer, Entity, Plan, Subscription, Purchase, Balance, Flag, and Billing Controls.\n\n\n\n- Map the user's billing request to one or more Autumn billing API calls.\n- Build the params for those calls before previewing.\n- Gather requirements in natural language; do not force the user to speak in Autumn field names.\n- Preview only when the params are complete enough to represent the intended billing action.\n\n\n\n\n\n- You MUST follow this checklist in order for every billing request.\n- Resolve targets with .\n- Choose the operation with .\n- Collect action-specific params with .\n- Resolve custom terms with .\n- Resolve timing with .\n- Resolve invoice, checkout, and proration behavior with .\n- If one ambiguity changes which of the other questions apply, resolve it first on its own before gathering the rest.\n- Gather all remaining missing questions from the checklist and ask them together.\n- If there are no missing questions, call the preview tool.\n- Surface the preview's immediate billing impact, then obtain approval via your client's approval mechanism.\n- If params change, update them and repeat from the relevant checklist step.\n- Once approved, apply the exact previewed billing action.\n\n\n\n\n\n- **A mutating billing action requires approval before it takes effect — obtain it via your client's approval mechanism.**\n- Don't propose or promise steps outside what your tools can do. If the goal isn't reachable, say so plainly rather than inventing a workaround.\n- Read this full resource before billing work and follow sections in order; later sections can define params that must be resolved before previewing.\n- Monetary amounts are major currency units: `$1,150` -> `1150`, not `115000`.\n- Slack billing requests usually expect immediate effect; use `plan_schedule: \"immediate\"` unless the user asks for end-of-cycle or future timing.\n- If using `invoice_mode` and the customer has no email, ask for the email and call `updateCustomer` before previewing.\n- Ask independent missing questions together in one concise message, using one bullet point per question.\n- While gathering params, ask only for values needed to build the billing request; do not explain plan internals unless the user asks.\n- If a customization is inferred, surface it for confirmation before previewing or writing. If its intent is ambiguous, ask before building — don't resolve it silently. When surfacing a customization, describe it as a patch (what was added/removed/changed vs the catalog plan), not a full restatement of every feature.\n- If the user gives an included credit/feature amount and the plan has a prepaid item for that feature, clarify whether they mean the quantity or a customization of the item, unless it's clear.\n- Before any trial action, re-read the Trials section in `autumn://docs/concepts`.\n- Adding a trial for a customer who already has a paid subscription resets the Stripe billing cycle; warn the user and offer the `on_end: \"revert\"` flow, then let them choose.\n\n\n\n\n\n- Resolve IDs from the user's message before choosing params: customer, optional entity, and plan(s).\n- If preloaded `listPlans` / `listFeatures` results are present, treat them as already-run tool results. Do not call them again unless the needed record is absent or the user asks to refresh.\n- Search remaining unknowns with the relevant lookup tools; run independent lookups in parallel.\n- If the user gives explicit IDs, verify they exist with the same lookup tools.\n- Do not create plans. If the customer or entity is missing, ask whether to create it.\n- If an explicit customer/entity/plan ID is found, move on. For plans, a perfect or very close name match is enough only if it maps to exactly one plan.\n- If sibling plans share a base name but differ by variant (e.g. `scale` vs `scale_yearly`), the name alone is not a match; ask which variant before previewing, especially for interval-specific or custom terms.\n- If an ID had to be inferred from a name or description, confirm the match with the user before moving on.\n- If the found customer has no email, remember that and ask for it before any invoice or checkout step.\n\n\n\n\n\n \n\n - Usually choose `attach` or `updateSubscription`.\n - Use `createSchedule` for multiple phases or when the request needs explicit control over future billing state.\n - Use `attach` for a single plan attach even when it has `starts_at` or `ends_at`.\n - Check the customer's existing subscriptions before choosing the action.\n\n \n\n \n\n - Use when the customer/entity is not already on the target plan: new subscription, purchase, upgrade, downgrade, add-on, or one-off top-up.\n - If they already have the target recurring plan, do not attach it again; use `updateSubscription`.\n - One-off plans are always attached because each purchase/top-up is a new purchase.\n - `attach` handles transitions: switching main plans expires the replaced plan, while add-ons are additive.\n - Autumn determines upgrade vs downgrade from normalized base and prepaid prices.\n - By default, `attach` tries to add the new plan to an existing Stripe subscription when possible; mid-cycle changes on that subscription can charge or credit prorations.\n - Downgrades default to end-of-cycle scheduling through `plan_schedule`; upgrades usually apply immediately.\n\n \n\n \n\n - Use when changing a plan the customer/entity is currently on.\n - Main uses: prepaid feature quantity changes, cancel now/end-of-cycle, uncancel, custom plan edits, version/trial changes, and discounts.\n - Use it for modifying the existing subscription state, not for moving to a different plan the customer is not on.\n\n \n\n \n\n - Use for explicit dated phases: contracts, future changes, multi-year pricing, or sequences like Pro for 6 months then Starter.\n - Treat it like replacing the customer/entity's billing configuration for that scope with the listed phase states.\n - The immediate phase previews like a transition from current state to the first phase, so prorations may apply.\n - Each phase must still be valid: do not put two non-add-on main plans from the same group in one phase.\n - Watch contracts for dates, year-by-year fees, ramps, delayed downgrades, or different packages over time.\n\n \n\n \n\n - Existing subscriptions decide `attach` vs `updateSubscription`; one-off plans are the main exception and are always attached.\n - Preview responses describe the immediate billing impact: what is charged or credited now, plus future-cycle details when present.\n - Schedules are explicit phase state. Use them when attach/update cannot express the required future configuration or level of control.\n\n \n\n\n\n\n\n- After target resolution, collect the params specific to the selected action.\n- Before previewing, resolve any required `customize` params identified in .\n- `attach`\n - If the plan has prepaid items and quantity is missing, ask for the quantity before previewing.\n - If the prepaid quantity is known, include `feature_quantities`. Undefined `feature_quantities` defaults to 0 for that feature.\n - `feature_quantities.quantity` is inclusive of the plan item's included amount. Example: if 5,000 credits are included and $10/100 credits after, passing 6,000 means only the extra 1,000 credits are charged.\n- `updateSubscription`\n - If intent is ambiguous, clarify before previewing; e.g. cancel now vs cancel at end of cycle.\n - Quantity change: include `feature_quantities`.\n - Cancel or uncancel: include `cancel_action`.\n - Custom terms: include `customize`.\n - Version, trial, or discount change: include the matching fields. (less common intent)\n- `createSchedule`\n - Build ordered `phases[]`.\n - Each phase needs timing (`starts_at` or `starting_after`) and at least one `plans[]` entry.\n- If a missing value changes billing impact, ask before previewing.\n\n\n\n and no unit (e.g. \"1k/yr\", \"2k/mo\") is ambiguous between `customize.price` and a feature quantity (credits/seats); clarify which before building the customize, and read the same pattern consistently across the request.\n- A list of what a customer \"gets\" is ambiguous: restating the plan, adding on top, or the exact set (items not listed are removed/zeroed). If the reading changes what they receive vs the catalog plan, ask which before building.\n- \"Features\" may mean only some items (e.g. booleans) or include credits/metered items; clarify scope before removing anything priced.\n- Plan item changes are always PATCH-style: `customize.add_items` and `customize.remove_items` change selected items.\n- Never use `customize.items` (PUT-style full replacement) or `update_items`. To make the plan's items the exact set, remove the unwanted ones with `remove_items` and add the missing ones with `add_items`.\n- Each `remove_items` entry is a filter for items to remove from the plan.\n- Include `billing_method`, `interval`, or `interval_count` in the filter when `feature_id` alone could match multiple items.\n- Replace an item's configuration: remove the old item and add the new version in the same PATCH-style `customize`.\n- When the same outcome can be expressed multiple ways, prefer the customization that preserves the catalog plan's existing item structure: same-shape customizations keep the customer consistent with others on the plan and with their existing update/quantity flows.\n\n \n A plan prices `credits` as a prepaid, volume-tiered item (ladder\n `10k=$90, 50k=$400, inf=$700`). To give a customer 20k credits at a custom\n $150/mo, add a `20k=$150` tier into the existing ladder:\n\n ```json\n {\n \"customize\": {\n \"remove_items\": [{ \"feature_id\": \"credits\", \"billing_method\": \"prepaid\" }],\n \"add_items\": [\n {\n \"feature_id\": \"credits\",\n \"price\": {\n \"billing_method\": \"prepaid\",\n \"interval\": \"month\",\n \"tier_behavior\": \"volume\",\n \"tiers\": [\n { \"to\": 10000, \"flat_amount\": 90 },\n { \"to\": 20000, \"flat_amount\": 150 },\n { \"to\": 50000, \"flat_amount\": 400 },\n { \"to\": \"inf\", \"flat_amount\": 700 }\n ]\n }\n }\n ]\n },\n \"feature_quantities\": [{ \"feature_id\": \"credits\", \"quantity\": 20000 }]\n }\n ```\n\n Note: the new tier is added into the plan's existing tiers — carry the whole\n ladder over; don't replace it with just the custom tier or a flat base price.\n \n \n- If a plan name/id/context suggests an Enterprise or custom placeholder plan and the plan has no base price, and no commercial terms were specified, ask the user whether they want to customize the base price.\n\nUse cases:\n\n- `updateSubscription`: customize the plan configuration the customer is already on.\n ```json\n {\n \"customer_id\": \"cus_123\",\n \"plan_id\": \"pro\",\n \"customize\": { \"add_items\": [{ \"feature_id\": \"sso\" }] }\n }\n ```\n\n- `attach`: attach a plan with customer-specific base price or item changes.\n ```json\n {\n \"customer_id\": \"cus_123\",\n \"plan_id\": \"pro\",\n \"customize\": {\n \"price\": { \"amount\": 50, \"interval\": \"month\" },\n \"add_items\": [{ \"feature_id\": \"credits\", \"included\": 5000 }]\n }\n }\n ```\n\n- `createSchedule`: customize the plan inside the phase that needs custom terms.\n ```json\n {\n \"customer_id\": \"cus_123\",\n \"phases\": [\n { \"starts_at\": \"now\", \"plans\": [{ \"plan_id\": \"pro\" }] },\n {\n \"starts_at\": \"2027-06-12T00:00:00Z\",\n \"plans\": [\n {\n \"plan_id\": \"pro\",\n \"customize\": { \"price\": { \"amount\": 75, \"interval\": \"month\" } }\n }\n ]\n }\n ]\n }\n ```\n\nExamples:\n\n- Change base price:\n ```json\n { \"customize\": { \"price\": { \"amount\": 50, \"interval\": \"month\" } } }\n ```\n\n- Add a boolean feature:\n ```json\n { \"customize\": { \"add_items\": [{ \"feature_id\": \"sso\" }] } }\n ```\n\n- Remove a feature:\n ```json\n { \"customize\": { \"remove_items\": [{ \"feature_id\": \"audit_logs\" }] } }\n ```\n\n- Change included amount:\n ```json\n {\n \"customize\": {\n \"remove_items\": [{ \"feature_id\": \"credits\" }],\n \"add_items\": [{ \"feature_id\": \"credits\", \"included\": 5000 }]\n }\n }\n ```\n\n- Change included amount and reset interval:\n ```json\n {\n \"customize\": {\n \"remove_items\": [{ \"feature_id\": \"credits\" }],\n \"add_items\": [\n {\n \"feature_id\": \"credits\",\n \"included\": 5000,\n \"reset\": { \"interval\": \"month\" }\n }\n ]\n }\n }\n ```\n\n- Change only the monthly item when the same feature also has a lifetime item:\n ```json\n {\n \"customize\": {\n \"remove_items\": [\n {\n \"feature_id\": \"credits\",\n \"billing_method\": \"prepaid\",\n \"interval\": \"month\"\n }\n ],\n \"add_items\": [\n {\n \"feature_id\": \"credits\",\n \"included\": 5000,\n \"reset\": { \"interval\": \"month\" }\n }\n ]\n }\n }\n ```\n\n- Change prepaid to usage-based:\n ```json\n {\n \"customize\": {\n \"remove_items\": [{ \"feature_id\": \"credits\" }],\n \"add_items\": [\n {\n \"feature_id\": \"credits\",\n \"included\": 0,\n \"price\": {\n \"amount\": 0.01,\n \"interval\": \"month\",\n \"billing_method\": \"usage_based\"\n }\n }\n ]\n }\n }\n ```\n\n\n\n\n\n \n\n - Do not set `starts_at` or `ends_at` unless the user gives a date, duration, backdate, future start, or explicit end date.\n - If timing is ambiguous and affects billing impact, ask before previewing.\n\n \n\n \n\n - To attach now, explicitly set `plan_schedule: \"immediate\"`; omitting it can schedule a lower- or zero-base-price plan for end of cycle.\n - A downgrade (incoming base price genuinely lower than the current plan's) should be flagged to the user, asking whether to schedule it for end of cycle. A no-base-price plan (e.g. Enterprise/custom, priced per customer) is not a downgrade.\n - Use `starts_at` for single-plan backdates or future starts; do not use `createSchedule` just for this.\n - Backdating is only allowed when the customer has no existing Stripe subscription. If the API rejects it, explain that constraint.\n - For future billing start with immediate access, set future `starts_at` and `enable_plan_immediately: true`; otherwise the user's plan is created with `scheduled` status in Autumn and access starts on the specified `starts_at`.\n - Use `ends_at` only when the user gives an explicit end date or duration.\n\n \n\n \n\n - Scheduling is only relevant for canceling at end of cycle.\n - Immediate cancel, uncancel, quantity changes, and customizations do not need schedule params.\n\n \n\n \n\n - If the user describes phases relatively and gives no concrete dates (e.g. \"year 1 $10k, year 2 $20k\"), use `starts_at: \"now\"` on phase 1 and `starting_after` on later phases.\n - If the user gives concrete phase dates, use explicit `starts_at` values; later `starts_at` values must align exactly with the intended boundary.\n - Use a historical first `starts_at` only when the user explicitly asks for a past start.\n - Future first-phase `starts_at` is not supported today.\n - Resolve every phase's plan and customization before previewing.\n\n \n\n \n\n - Autumn date params and responses are epoch milliseconds.\n - Never interpret epoch milliseconds manually; use `dateToEpochMilliseconds`, `epochMillisecondsToDate`, or the most convenient accurate tool available, such as bash date utilities.\n - Prefer ISO dates/timestamps in params when the schema allows them; the tool will convert.\n - Present dates as `12 Jun 2026`; include `HH:MM` only when time matters.\n\n \n\n\n\n\n\n\n\n- Default operator-led billing actions to invoice mode: `invoice_mode.enabled: true` and `invoice_mode.finalize: false`, and grant access now (see for which field).\n- Use invoice mode even when the immediate charge is $0, unless the user asks for checkout, self-serve, or direct charging.\n- This grants access now while creating a draft Stripe invoice that the operator can review, edit, and send.\n- Use explicit net terms from the user or contract in `invoice_mode.net_terms_days`; otherwise do not ask just to set net terms.\n- If the customer has no email, ask for it and update the customer before previewing invoice or checkout flows.\n\n\n\n\n\n- Top-level `enable_plan_immediately` grants access now whenever payment is deferred or pending (invoice unpaid, checkout incomplete, or future `starts_at`) — a superset of `invoice_mode.enable_plan_immediately`, which only covers the invoice-unpaid case.\n- For `createSchedule` and `attach`, set top-level `enable_plan_immediately: true` instead of `invoice_mode.enable_plan_immediately`.\n- `updateSubscription` has no top-level field; keep using `invoice_mode.enable_plan_immediately` there.\n\n\n\n\n\n- Use checkout only when the user wants a payment link or checkout session to send to the customer.\n- For checkout, omit `invoice_mode`, set `redirect_mode: \"always\"`, and set `enable_plan_immediately: true`.\n- If the user might be asking for checkout but did not say so clearly, clarify before previewing.\n\n\n\n\n\n- If the user wants self-serve-style billing or immediate card charging, clarify before omitting `invoice_mode`.\n- Without `invoice_mode`, eligible plan changes may charge the customer immediately.\n\n\n\n\n\n- Default proration to `none` so the preview starts with no immediate prorated charge or credit.\n- If the customer has no existing subscriptions, do not pass `proration_behavior: \"none\"`; new subscriptions do not allow it.\n- Use the endpoint's field name: `proration_behavior` for attach/updateSubscription, `billing_behavior` for createSchedule.\n- Use `prorate_immediately` only when the user asks for prorations, immediate true-up, or immediate credits/charges.\n\n\n\n\n\n\n\n- Preview only after action, target IDs, quantities, customization, timing, and billing behavior are known or intentionally defaulted.\n- If missing information could change immediate charges, access timing, or scheduled state, ask before previewing.\n- The main purpose of preview is to determine immediate billing impact: `total`, `currency`, and `line_items`.\n- Summarize the preview's impact before the write.\n- Lead with immediate impact: amount due now, no immediate charge, or credit.\n- Include only preview facts that affect approval; avoid repeating context the user already resolved.\n- If `next_cycle` exists, explain the next event: date, amount, and likely reason such as renewal, trial end, cancellation, downgrade, phase change, or nearest multi-interval event.\n- Mention material state changes from `incoming` and `outgoing`, but treat them as supporting context if they look stale or incomplete.\n- Convert preview timestamps before presenting dates.\n- Apply only the exact previewed request. If params change, preview again.\n\n\n\n\n\n- After the billing action succeeds, respond as concisely as possible: say the action completed successfully.\n- Surface any returned customer-facing URL, especially `payment_url` or `invoice.hosted_invoice_url`.\n- If an `invoice` is returned, mention its `status`, `stripe_id`, and hosted URL when present.\n- If `invoice.hosted_invoice_url` is missing but `invoice.stripe_id` exists, surface the Stripe dashboard invoice URL: sandbox `https://dashboard.stripe.com/test/invoices/{stripe_id}`, live `https://dashboard.stripe.com/invoices/{stripe_id}`.\n- If `required_action` is returned, explain the required payment action and include `payment_url` if present.\n- If the action fails, state that it failed and quote the server error/status clearly.\n- Do not re-summarize the full preview after completion unless the user asks.\n\n" + } +] satisfies AutumnMcpResourceDoc[]; diff --git a/packages/mcp/src/resources-v2/index.ts b/packages/mcp/src/resources-v2/index.ts index b2670a214..83a005408 100644 --- a/packages/mcp/src/resources-v2/index.ts +++ b/packages/mcp/src/resources-v2/index.ts @@ -3,6 +3,7 @@ import type { MCPServerResources } from "@mastra/mcp"; import { parseResourceMarkdown } from "../resources/compileResources.js"; import type { AutumnMcpResourceDoc } from "../resources/types.js"; import { getAutumnMcpRuntimeConfig } from "../server/runtime.js"; +import { createStaticAutumnMcpResources } from "./staticResources.js"; const billingResource = { name: "billing", @@ -157,28 +158,6 @@ const compileResources = ({ ]); }; -export const createStaticAutumnMcpResources = ( - docs: AutumnMcpResourceDoc[], -): MCPServerResources => ({ - listResources: async () => - docs.map((doc) => ({ - uri: doc.uri, - name: doc.name, - title: doc.title, - description: doc.description, - mimeType: "text/markdown", - size: doc.text.length, - annotations: { audience: doc.audience, priority: doc.priority }, - })), - getResourceContent: async ({ uri }) => { - const doc = docs.find((entry) => entry.uri === uri); - if (!doc) { - throw new Error(`Unknown Autumn MCP resource: ${uri}`); - } - return { text: doc.text }; - }, -}); - export const createAutumnMcpResources = ({ baseUrl, }: { diff --git a/packages/mcp/src/resources-v2/staticResources.ts b/packages/mcp/src/resources-v2/staticResources.ts new file mode 100644 index 000000000..dfb325581 --- /dev/null +++ b/packages/mcp/src/resources-v2/staticResources.ts @@ -0,0 +1,24 @@ +import type { MCPServerResources } from "@mastra/mcp"; +import type { AutumnMcpResourceDoc } from "../resources/types.js"; + +export const createStaticAutumnMcpResources = ( + docs: AutumnMcpResourceDoc[], +): MCPServerResources => ({ + listResources: async () => + docs.map((doc) => ({ + uri: doc.uri, + name: doc.name, + title: doc.title, + description: doc.description, + mimeType: "text/markdown", + size: doc.text.length, + annotations: { audience: doc.audience, priority: doc.priority }, + })), + getResourceContent: async ({ uri }) => { + const doc = docs.find((entry) => entry.uri === uri); + if (!doc) { + throw new Error(`Unknown Autumn MCP resource: ${uri}`); + } + return { text: doc.text }; + }, +}); diff --git a/packages/mcp/src/server/createServer.ts b/packages/mcp/src/server/createServer.ts new file mode 100644 index 000000000..bb0a870ab --- /dev/null +++ b/packages/mcp/src/server/createServer.ts @@ -0,0 +1,37 @@ +import { MCPServer } from "@mastra/mcp"; +import { createRawAutumnOperationTools } from "../tools/index.js"; +import { + type AutumnMcpRuntimeConfig, + setAutumnMcpRuntimeConfig, +} from "./runtime.js"; + +export type AutumnMcpServerOptions = { + description?: string | undefined; + id?: string | undefined; + instructions: string; + jsonSchemaValidator?: ConstructorParameters< + typeof MCPServer + >[0]["jsonSchemaValidator"]; + resources: ConstructorParameters[0]["resources"]; + runtime?: AutumnMcpRuntimeConfig | undefined; +}; + +export const createConfiguredAutumnOperationsMCPServer = ( + options: AutumnMcpServerOptions, +) => { + if (options.runtime) { + setAutumnMcpRuntimeConfig(options.runtime); + } + + return new MCPServer({ + id: options.id ?? "autumn-mcp", + name: "Autumn MCP", + version: "0.0.1", + description: + options.description ?? "Operate on Autumn customers, plans, and billing.", + instructions: options.instructions, + tools: createRawAutumnOperationTools(), + resources: options.resources, + jsonSchemaValidator: options.jsonSchemaValidator, + }); +}; diff --git a/packages/mcp/src/server/server.ts b/packages/mcp/src/server/server.ts index 49b66e633..4c9888a74 100644 --- a/packages/mcp/src/server/server.ts +++ b/packages/mcp/src/server/server.ts @@ -1,36 +1,24 @@ -import { MCPServer } from "@mastra/mcp"; import { autumnMcpResources } from "../resources/index.js"; import { autumnMcpInstructions } from "../resources-v2/mcpInstructions.js"; -import { createRawAutumnOperationTools } from "../tools/index.js"; import { - type AutumnMcpRuntimeConfig, - setAutumnMcpRuntimeConfig, -} from "./runtime.js"; + createConfiguredAutumnOperationsMCPServer, + type AutumnMcpServerOptions as ConfiguredAutumnMcpServerOptions, +} from "./createServer.js"; +import type { AutumnMcpRuntimeConfig } from "./runtime.js"; export type AutumnMcpServerOptions = { instructions?: string | undefined; - jsonSchemaValidator?: ConstructorParameters< - typeof MCPServer - >[0]["jsonSchemaValidator"]; - resources?: ConstructorParameters[0]["resources"]; + jsonSchemaValidator?: ConfiguredAutumnMcpServerOptions["jsonSchemaValidator"]; + resources?: ConfiguredAutumnMcpServerOptions["resources"]; runtime?: AutumnMcpRuntimeConfig | undefined; }; export const createAutumnOperationsMCPServer = ( options: AutumnMcpServerOptions = {}, -) => { - if (options.runtime) { - setAutumnMcpRuntimeConfig(options.runtime); - } - - return new MCPServer({ - id: "autumn-mcp", - name: "Autumn MCP", - version: "0.0.1", - description: "Operate on Autumn customers, plans, and billing.", +) => + createConfiguredAutumnOperationsMCPServer({ instructions: options.instructions ?? autumnMcpInstructions, - tools: createRawAutumnOperationTools(), resources: options.resources ?? autumnMcpResources, jsonSchemaValidator: options.jsonSchemaValidator, + runtime: options.runtime, }); -};