diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 788b0fa7..97bce112 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.22.0" + ".": "0.23.0" } diff --git a/.stats.yml b/.stats.yml index f9ca176c..6acc2379 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 45 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/flowglad%2Fflowglad-a1eeb4fac4d6f2e5a1bed0a6ef5abfc4172e8d989c24343860b8a07a389ea65b.yml -openapi_spec_hash: 1bd6007e37eeb62e1dc85da4f939ed1d -config_hash: b3cf56eeb5f18b49019b9be6e30263c0 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/flowglad%2Fflowglad-71d495f9b7d8f8c9d80340c0db0a71352f19b22a53688dbc268c7d9137390f2e.yml +openapi_spec_hash: c6293dd58700ec577dc2e10f8eb8f061 +config_hash: 3f7672efb046f15f1ee86bcb480da1a2 diff --git a/CHANGELOG.md b/CHANGELOG.md index eb00559d..01675d81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,39 @@ # Changelog +## 0.23.0 (2025-11-25) + +Full Changelog: [v0.22.0...v0.23.0](https://github.com/flowglad/flowglad-node/compare/v0.22.0...v0.23.0) + +### Features + +* **api:** manual updates ([1a19816](https://github.com/flowglad/flowglad-node/commit/1a19816abcef63fe94de504d04586c5ceb3ed58e)) +* **api:** update via SDK Studio ([539f4c4](https://github.com/flowglad/flowglad-node/commit/539f4c4518cb92b9d71ae7c890f55498cbf47d88)) +* **mcp:** enable optional code execution tool on http mcp servers ([090ca8f](https://github.com/flowglad/flowglad-node/commit/090ca8fb6207a056c1f4dc41fbd3b457bcc7007d)) + + +### Bug Fixes + +* **mcpb:** pin @anthropic-ai/mcpb version ([c8bf746](https://github.com/flowglad/flowglad-node/commit/c8bf74639585d555710ec2a68d1a51224bd12d8f)) +* **mcp:** return tool execution error on jq failure ([047276b](https://github.com/flowglad/flowglad-node/commit/047276bc4d3bd247613d349f63f1bdbdb60b1416)) + + +### Chores + +* **internal:** codegen related update ([f45deec](https://github.com/flowglad/flowglad-node/commit/f45deec19f15bc5e89faa32905fdf0b1ef03abe5)) +* **internal:** codegen related update ([d8c690d](https://github.com/flowglad/flowglad-node/commit/d8c690d0b0b316915e3276cee1aa2c07099a6ddf)) +* **internal:** grammar fix (it's -> its) ([ceea4b0](https://github.com/flowglad/flowglad-node/commit/ceea4b006184694f7fdd6c38a6b9efc8534270c0)) +* mcp code tool explicit error message when missing a run function ([9f95cba](https://github.com/flowglad/flowglad-node/commit/9f95cba7b813396b26890f8ce60f895eb066a321)) +* **mcp:** add friendlier MCP code tool errors on incorrect method invocations ([69bd7b0](https://github.com/flowglad/flowglad-node/commit/69bd7b061e2806e6843fab6ef5c7ef6c42dc230e)) +* **mcp:** add line numbers to code tool errors ([48cfd3a](https://github.com/flowglad/flowglad-node/commit/48cfd3a78bd865fd461cacabc85b720e12cbedee)) +* **mcp:** clarify http auth error ([8a6a2cb](https://github.com/flowglad/flowglad-node/commit/8a6a2cbd9cae370bcbf1bd8c24a2d2b70ca0f8b4)) +* **mcp:** upgrade jq-web ([51ca5f4](https://github.com/flowglad/flowglad-node/commit/51ca5f4ac13ccf1b7d2c7d11d3996005c7362717)) +* use structured error when code execution tool errors ([20cc664](https://github.com/flowglad/flowglad-node/commit/20cc6648e532399cbc3517e57badf36a61e6cd17)) + + +### Documentation + +* **mcp:** add a README button for one-click add to Cursor ([d0679c8](https://github.com/flowglad/flowglad-node/commit/d0679c80b534c7f4d853a494685333f2ef761c61)) +* **mcp:** add a README link to add server to VS Code or Claude Code ([5526204](https://github.com/flowglad/flowglad-node/commit/55262048deb2efe9c80d17ca998fe10bf6dd48af)) + ## 0.22.0 (2025-10-18) Full Changelog: [v0.21.0...v0.22.0](https://github.com/flowglad/flowglad-node/compare/v0.21.0...v0.22.0) diff --git a/api.md b/api.md index dd4938c2..94a7a2ba 100644 --- a/api.md +++ b/api.md @@ -139,7 +139,7 @@ Types: - CustomerClientSelectSchema - ToggleSubscriptionItemFeatureRecord -- UsageCreditGrantSubscriptionItemFeatureClientSelectSchema +- UsageCreditGrantSubscriptionItemFeatureRecord - CustomerCreateResponse - CustomerRetrieveResponse - CustomerUpdateResponse diff --git a/package.json b/package.json index d6199c55..de17d511 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@flowglad/node", - "version": "0.22.0", + "version": "0.23.0", "description": "The official TypeScript library for the Flowglad API", "author": "Flowglad ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index ef4aa9f5..eccd1d2e 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -34,12 +34,36 @@ For clients with a configuration JSON, it might look something like this: } ``` +### Cursor + +If you use Cursor, you can install the MCP server by using the button below. You will need to set your environment variables +in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > New MCP Server. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=@flowglad/node-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBmbG93Z2xhZC9ub2RlLW1jcCJdLCJlbnYiOnsiRkxPV0dMQURfU0VDUkVUX0tFWSI6IlNldCB5b3VyIEZMT1dHTEFEX1NFQ1JFVF9LRVkgaGVyZS4ifX0) + +### VS Code + +If you use MCP, you can install the MCP server by clicking the link below. You will need to set your environment variables +in VS Code's `mcp.json`, which can be found via Command Palette > MCP: Open User Configuration. + +[Open VS Code](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40flowglad%2Fnode-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40flowglad%2Fnode-mcp%22%5D%2C%22env%22%3A%7B%22FLOWGLAD_SECRET_KEY%22%3A%22Set%20your%20FLOWGLAD_SECRET_KEY%20here.%22%7D%7D) + +### Claude Code + +If you use Claude Code, you can install the MCP server by running the command below in your terminal. You will need to set your +environment variables in Claude Code's `.claude.json`, which can be found in your home directory. + +``` +claude mcp add --transport stdio flowglad_node_api --env FLOWGLAD_SECRET_KEY="Your FLOWGLAD_SECRET_KEY here." -- npx -y @flowglad/node-mcp +``` + ## Exposing endpoints to your MCP Client -There are two ways to expose endpoints as tools in the MCP server: +There are three ways to expose endpoints as tools in the MCP server: 1. Exposing one tool per endpoint, and filtering as necessary 2. Exposing a set of tools to dynamically discover and invoke endpoints from the API +3. Exposing a docs search tool and a code execution tool, allowing the client to write code to be executed against the TypeScript client ### Filtering endpoints and tools @@ -74,6 +98,18 @@ All of these command-line options can be repeated, combined together, and have c Use `--list` to see the list of available tools, or see below. +### Code execution + +If you specify `--tools=code` to the MCP server, it will expose just two tools: + +- `search_docs` - Searches the API documentation and returns a list of markdown results +- `execute` - Runs code against the TypeScript client + +This allows the LLM to implement more complex logic by chaining together many API calls without loading +intermediary results into its context window. + +The code execution itself happens in a Deno sandbox that has network access only to the base URL for the API. + ### Specifying the MCP Client Different clients have varying abilities to handle arbitrary tools and schemas. @@ -272,7 +308,7 @@ The following tools are available in this MCP server. - `retrieve_subscriptions` (`read`): Get Subscription - `list_subscriptions` (`read`): List Subscriptions - `adjust_subscriptions` (`write`): Note: Immediate adjustments are in private preview (Please let us know you use this feature: https://github.com/flowglad/flowglad/issues/616). Adjustments at the end of the current billing period are generally available. -- `cancel_subscriptions` (`write`): Cancel a Subscription +- `cancel_subscriptions` (`write`): Cancel Subscription ### Resource `usage_events`: diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index f0486f49..e913ebd3 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@flowglad/node-mcp", - "version": "0.22.0", + "version": "0.23.0", "description": "The official MCP Server for the Flowglad API", "author": "Flowglad ", "types": "dist/index.d.ts", @@ -36,8 +36,10 @@ "@valtown/deno-http-worker": "^0.0.21", "cors": "^2.8.5", "express": "^5.1.0", - "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz", + "fuse.js": "^7.1.0", + "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz", "qs": "^6.14.0", + "typescript": "5.8.3", "yargs": "^17.7.2", "zod": "^3.25.20", "zod-to-json-schema": "^3.24.5", @@ -47,7 +49,7 @@ "mcp-server": "dist/index.js" }, "devDependencies": { - "@anthropic-ai/mcpb": "^1.1.0", + "@anthropic-ai/mcpb": "1.1.0", "@types/cors": "^2.8.19", "@types/express": "^5.0.3", "@types/jest": "^29.4.0", @@ -64,8 +66,7 @@ "ts-morph": "^19.0.0", "ts-node": "^10.5.0", "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz", - "tsconfig-paths": "^4.0.0", - "typescript": "5.8.3" + "tsconfig-paths": "^4.0.0" }, "imports": { "@flowglad/node-mcp": ".", diff --git a/packages/mcp-server/src/code-tool-worker.ts b/packages/mcp-server/src/code-tool-worker.ts index d027bbcd..d3eff4ef 100644 --- a/packages/mcp-server/src/code-tool-worker.ts +++ b/packages/mcp-server/src/code-tool-worker.ts @@ -1,11 +1,208 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import util from 'node:util'; + +import Fuse from 'fuse.js'; +import ts from 'typescript'; + import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types'; import { Flowglad } from '@flowglad/node'; +function getRunFunctionNode( + code: string, +): ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | null { + const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true); + + for (const statement of sourceFile.statements) { + // Check for top-level function declarations + if (ts.isFunctionDeclaration(statement)) { + if (statement.name?.text === 'run') { + return statement; + } + } + + // Check for variable declarations: const run = () => {} or const run = function() {} + if (ts.isVariableStatement(statement)) { + for (const declaration of statement.declarationList.declarations) { + if (ts.isIdentifier(declaration.name) && declaration.name.text === 'run') { + // Check if it's initialized with a function + if ( + declaration.initializer && + (ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer)) + ) { + return declaration.initializer; + } + } + } + } + } + + return null; +} + +const fuse = new Fuse( + [ + 'client.invoices.list', + 'client.invoices.retrieve', + 'client.invoiceLineItems.list', + 'client.invoiceLineItems.retrieve', + 'client.pricingModels.clone', + 'client.pricingModels.create', + 'client.pricingModels.list', + 'client.pricingModels.retrieve', + 'client.pricingModels.retrieveDefault', + 'client.pricingModels.update', + 'client.checkoutSessions.create', + 'client.checkoutSessions.list', + 'client.checkoutSessions.retrieve', + 'client.products.create', + 'client.products.list', + 'client.products.retrieve', + 'client.products.update', + 'client.prices.create', + 'client.prices.list', + 'client.prices.update', + 'client.discounts.create', + 'client.discounts.list', + 'client.discounts.retrieve', + 'client.discounts.update', + 'client.customers.create', + 'client.customers.list', + 'client.customers.retrieve', + 'client.customers.retrieveBilling', + 'client.customers.update', + 'client.payments.list', + 'client.payments.refund', + 'client.payments.retrieve', + 'client.paymentMethods.list', + 'client.paymentMethods.retrieve', + 'client.subscriptions.adjust', + 'client.subscriptions.cancel', + 'client.subscriptions.create', + 'client.subscriptions.list', + 'client.subscriptions.retrieve', + 'client.usageEvents.create', + 'client.usageEvents.retrieve', + 'client.usageMeters.create', + 'client.usageMeters.list', + 'client.usageMeters.retrieve', + 'client.usageMeters.update', + ], + { threshold: 1, shouldSort: true }, +); + +function getMethodSuggestions(fullyQualifiedMethodName: string): string[] { + return fuse + .search(fullyQualifiedMethodName) + .map(({ item }) => item) + .slice(0, 5); +} + +const proxyToObj = new WeakMap(); +const objToProxy = new WeakMap(); + +type ClientProxyConfig = { + path: string[]; + isBelievedBad?: boolean; +}; + +function makeSdkProxy(obj: T, { path, isBelievedBad = false }: ClientProxyConfig): T { + let proxy: T = objToProxy.get(obj); + + if (!proxy) { + proxy = new Proxy(obj, { + get(target, prop, receiver) { + const propPath = [...path, String(prop)]; + const value = Reflect.get(target, prop, receiver); + + if (isBelievedBad || (!(prop in target) && value === undefined)) { + // If we're accessing a path that doesn't exist, it will probably eventually error. + // Let's proxy it and mark it bad so that we can control the error message. + // We proxy an empty class so that an invocation or construction attempt is possible. + return makeSdkProxy(class {}, { path: propPath, isBelievedBad: true }); + } + + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + return makeSdkProxy(value, { path: propPath, isBelievedBad }); + } + + return value; + }, + + apply(target, thisArg, args) { + if (isBelievedBad || typeof target !== 'function') { + const fullyQualifiedMethodName = path.join('.'); + const suggestions = getMethodSuggestions(fullyQualifiedMethodName); + throw new Error( + `${fullyQualifiedMethodName} is not a function. Did you mean: ${suggestions.join(', ')}`, + ); + } + + return Reflect.apply(target, proxyToObj.get(thisArg) ?? thisArg, args); + }, + + construct(target, args, newTarget) { + if (isBelievedBad || typeof target !== 'function') { + const fullyQualifiedMethodName = path.join('.'); + const suggestions = getMethodSuggestions(fullyQualifiedMethodName); + throw new Error( + `${fullyQualifiedMethodName} is not a constructor. Did you mean: ${suggestions.join(', ')}`, + ); + } + + return Reflect.construct(target, args, newTarget); + }, + }); + + objToProxy.set(obj, proxy); + proxyToObj.set(proxy, obj); + } + + return proxy; +} + +function parseError(code: string, error: unknown): string | undefined { + if (!(error instanceof Error)) return; + const message = error.name ? `${error.name}: ${error.message}` : error.message; + try { + // Deno uses V8; the first ":LINE:COLUMN" is the top of stack. + const lineNumber = error.stack?.match(/:([0-9]+):[0-9]+/)?.[1]; + // -1 for the zero-based indexing + const line = + lineNumber && + code + .split('\n') + .at(parseInt(lineNumber, 10) - 1) + ?.trim(); + return line ? `${message}\n at line ${lineNumber}\n ${line}` : message; + } catch { + return message; + } +} + const fetch = async (req: Request): Promise => { const { opts, code } = (await req.json()) as WorkerInput; + if (code == null) { + return Response.json( + { + message: + 'The code param is missing. Provide one containing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } + + const runFunctionNode = getRunFunctionNode(code); + if (!runFunctionNode) { + return Response.json( + { + message: + 'The code is missing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```', + } satisfies WorkerError, + { status: 400, statusText: 'Code execution error' }, + ); + } + const client = new Flowglad({ ...opts, }); @@ -22,21 +219,17 @@ const fetch = async (req: Request): Promise => { }; try { let run_ = async (client: any) => {}; - eval(` - ${code} - run_ = run; - `); - const result = await run_(client); + eval(`${code}\nrun_ = run;`); + const result = await run_(makeSdkProxy(client, { path: ['client'] })); return Response.json({ result, logLines, errLines, } satisfies WorkerSuccess); } catch (e) { - const message = e instanceof Error ? e.message : undefined; return Response.json( { - message, + message: parseError(code, e), } satisfies WorkerError, { status: 400, statusText: 'Code execution error' }, ); diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 9e8ab104..d2a20550 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -3,7 +3,7 @@ import { dirname } from 'node:path'; import { pathToFileURL } from 'node:url'; import Flowglad, { ClientOptions } from '@flowglad/node'; -import { Endpoint, ContentBlock, Metadata } from './tools/types'; +import { ContentBlock, Endpoint, Metadata, ToolCallResult } from './tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; @@ -12,7 +12,7 @@ import { WorkerInput, WorkerError, WorkerSuccess } from './code-tool-types'; /** * A tool that runs code against a copy of the SDK. * - * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, + * Instead of exposing every endpoint as its own tool, which uses up too many tokens for LLMs to use at once, * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then * a generic endpoint that can be used to invoke any endpoint with the provided arguments. * @@ -23,7 +23,7 @@ export async function codeTool(): Promise { const tool: Tool = { name: 'execute', description: - 'Runs Typescript code to interact with the API.\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client, and it will be run.\nDo not initialize a client, but instead use the client that you are given as a parameter.\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', + 'Runs JavaScript code to interact with the API.\n\nYou are a skilled programmer writing code to interface with the service.\nDefine an async function named "run" that takes a single parameter of an initialized client named "client", and it will be run.\nWrite code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```\n\nYou will be returned anything that your function returns, plus the results of any console.log statements.\nIf any code triggers an error, the tool will return an error response, so you do not need to add error handling unless you want to output something more helpful than the raw error.\nIt is not necessary to add comments to code, unless by adding those comments you believe that you can generate better code.\nThis code will run in a container, and you will not be able to use fetch or otherwise interact with the network calls other than through the client you are given.\nAny variables you define won\'t live between successive uses of this call, so make sure to return or log any data you might need later.', inputSchema: { type: 'object', properties: { code: { type: 'string' } } }, }; @@ -31,7 +31,7 @@ export async function codeTool(): Promise { const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); const { workerPath } = await import('./code-tool-paths.cjs'); - const handler = async (client: Flowglad, args: unknown) => { + const handler = async (client: Flowglad, args: unknown): Promise => { const baseURLHostname = new URL(client.baseURL).hostname; const { code } = args as { code: string }; @@ -97,7 +97,7 @@ export async function codeTool(): Promise { } satisfies WorkerInput); req.write(body, (err) => { - if (err !== null && err !== undefined) { + if (err != null) { reject(err); } }); @@ -108,12 +108,12 @@ export async function codeTool(): Promise { if (resp.status === 200) { const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess; const returnOutput: ContentBlock | null = - result === null ? null - : result === undefined ? null - : { + result == null ? null : ( + { type: 'text', - text: typeof result === 'string' ? (result as string) : JSON.stringify(result), - }; + text: typeof result === 'string' ? result : JSON.stringify(result), + } + ); const logOutput: ContentBlock | null = logLines.length === 0 ? null @@ -133,10 +133,11 @@ export async function codeTool(): Promise { }; } else { const { message } = (await resp.json()) as WorkerError; - throw new Error(message); + return { + content: message == null ? [] : [{ type: 'text', text: message }], + isError: true, + }; } - } catch (e) { - throw e; } finally { worker.terminate(); } diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts index b1cea5b4..926bd1c4 100644 --- a/packages/mcp-server/src/dynamic-tools.ts +++ b/packages/mcp-server/src/dynamic-tools.ts @@ -14,7 +14,7 @@ function zodToInputSchema(schema: z.ZodSchema) { /** * A list of tools that expose all the endpoints in the API dynamically. * - * Instead of exposing every endpoint as it's own tool, which uses up too many tokens for LLMs to use at once, + * Instead of exposing every endpoint as its own tool, which uses up too many tokens for LLMs to use at once, * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then * a generic endpoint that can be used to invoke any endpoint with the provided arguments. * diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts index 1aa9a40c..eaae0fcf 100644 --- a/packages/mcp-server/src/filtering.ts +++ b/packages/mcp-server/src/filtering.ts @@ -12,3 +12,7 @@ export async function maybeFilter(jqFilter: unknown | undefined, response: any): async function jq(json: any, jqFilter: string) { return (await initJq).json(json, jqFilter); } + +export function isJqError(error: any): error is Error { + return error instanceof Error && 'stderr' in error; +} diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts index ec34ab47..84517003 100644 --- a/packages/mcp-server/src/http.ts +++ b/packages/mcp-server/src/http.ts @@ -46,12 +46,12 @@ const newServer = ({ }, mcpOptions, }); - } catch { + } catch (error) { res.status(401).json({ jsonrpc: '2.0', error: { code: -32000, - message: 'Unauthorized', + message: `Unauthorized: ${error instanceof Error ? error.message : error}`, }, }); return null; diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index 4fe3b987..b6ff5976 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -284,8 +284,10 @@ const coerceArray = (zodType: T) => ); const QueryOptions = z.object({ - tools: coerceArray(z.enum(['dynamic', 'all', 'docs'])).describe('Use dynamic tools or all tools'), - no_tools: coerceArray(z.enum(['dynamic', 'all', 'docs'])).describe('Do not use dynamic tools or all tools'), + tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe('Specify which MCP tools to use'), + no_tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe( + 'Specify which MCP tools to not use.', + ), tool: coerceArray(z.string()).describe('Include tools matching the specified names'), resource: coerceArray(z.string()).describe('Include tools matching the specified resources'), operation: coerceArray(z.enum(['read', 'write'])).describe( @@ -385,11 +387,16 @@ export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): M : queryOptions.tools?.includes('docs') ? true : defaultOptions.includeDocsTools; + let codeTools: boolean | undefined = + queryOptions.no_tools && queryOptions.no_tools?.includes('code') ? false + : queryOptions.tools?.includes('code') && defaultOptions.includeCodeTools ? true + : defaultOptions.includeCodeTools; + return { client: queryOptions.client ?? defaultOptions.client, includeDynamicTools: dynamicTools, includeAllTools: allTools, - includeCodeTools: undefined, + includeCodeTools: codeTools, includeDocsTools: docsTools, filters, capabilities: clientCapabilities, diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index d00923c3..16875513 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -34,7 +34,7 @@ export const newMcpServer = () => new McpServer( { name: 'flowglad_node_api', - version: '0.22.0', + version: '0.23.0', }, { capabilities: { tools: {}, logging: {} } }, ); diff --git a/packages/mcp-server/src/tools/checkout-sessions/create-checkout-sessions.ts b/packages/mcp-server/src/tools/checkout-sessions/create-checkout-sessions.ts index 817d180e..85ad5cec 100644 --- a/packages/mcp-server/src/tools/checkout-sessions/create-checkout-sessions.ts +++ b/packages/mcp-server/src/tools/checkout-sessions/create-checkout-sessions.ts @@ -33,10 +33,6 @@ export const tool: Tool = { type: 'string', description: 'The id of the Customer for this purchase session, as defined in your system', }, - priceId: { - type: 'string', - description: 'The ID of the price the customer shall purchase', - }, successUrl: { type: 'string', description: 'The URL to redirect to after the purchase is successful', @@ -63,13 +59,23 @@ export const tool: Tool = { description: 'Whether to preserve the billing cycle anchor date in the case that the customer already has an active subscription that renews. If not provided, defaults to false.', }, + priceId: { + type: 'string', + description: + 'The ID of the price the customer shall purchase. If not provided, priceSlug is required.', + }, + priceSlug: { + type: 'string', + description: + 'The slug of the price the customer shall purchase. If not provided, priceId is required.', + }, quantity: { type: 'number', description: 'The quantity of the purchase or subscription created when this checkout session succeeds. Ignored if the checkout session is of type `invoice`.', }, }, - required: ['cancelUrl', 'customerExternalId', 'priceId', 'successUrl', 'type'], + required: ['cancelUrl', 'customerExternalId', 'successUrl', 'type'], }, { type: 'object', @@ -82,10 +88,6 @@ export const tool: Tool = { type: 'string', description: 'The URL to redirect to after the purchase is canceled or fails', }, - priceId: { - type: 'string', - description: 'The ID of the price the customer shall purchase', - }, successUrl: { type: 'string', description: 'The URL to redirect to after the purchase is successful', @@ -112,13 +114,23 @@ export const tool: Tool = { description: 'Whether to preserve the billing cycle anchor date in the case that the customer already has an active subscription that renews. If not provided, defaults to false.', }, + priceId: { + type: 'string', + description: + 'The ID of the price the customer shall purchase. If not provided, priceSlug is required.', + }, + priceSlug: { + type: 'string', + description: + "The slug of the price the customer shall purchase from the organization's default pricing model. If not provided, priceId is required.", + }, quantity: { type: 'number', description: 'The quantity of the purchase or subscription created when this checkout session succeeds. Ignored if the checkout session is of type `invoice`.', }, }, - required: ['anonymous', 'cancelUrl', 'priceId', 'successUrl', 'type'], + required: ['anonymous', 'cancelUrl', 'successUrl', 'type'], }, { type: 'object', @@ -131,9 +143,6 @@ export const tool: Tool = { type: 'string', description: 'The id of the Customer for this purchase session, as defined in your system', }, - priceId: { - type: 'string', - }, successUrl: { type: 'string', description: 'The URL to redirect to after the purchase is successful', @@ -161,14 +170,7 @@ export const tool: Tool = { 'Whether to preserve the billing cycle anchor date in the case that the customer already has an active subscription that renews. If not provided, defaults to false.', }, }, - required: [ - 'cancelUrl', - 'customerExternalId', - 'priceId', - 'successUrl', - 'targetSubscriptionId', - 'type', - ], + required: ['cancelUrl', 'customerExternalId', 'successUrl', 'targetSubscriptionId', 'type'], }, { type: 'object', diff --git a/packages/mcp-server/src/tools/customers/list-customers.ts b/packages/mcp-server/src/tools/customers/list-customers.ts index d1554085..81de4803 100644 --- a/packages/mcp-server/src/tools/customers/list-customers.ts +++ b/packages/mcp-server/src/tools/customers/list-customers.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.customers.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.customers.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/customers/retrieve-customers.ts b/packages/mcp-server/src/tools/customers/retrieve-customers.ts index 1bdc19fd..8eccf796 100644 --- a/packages/mcp-server/src/tools/customers/retrieve-customers.ts +++ b/packages/mcp-server/src/tools/customers/retrieve-customers.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -42,7 +42,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { externalId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.customers.retrieve(externalId))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.customers.retrieve(externalId))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/customers/update-customers.ts b/packages/mcp-server/src/tools/customers/update-customers.ts index e69eecd7..a51c8f49 100644 --- a/packages/mcp-server/src/tools/customers/update-customers.ts +++ b/packages/mcp-server/src/tools/customers/update-customers.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -70,7 +70,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { externalId, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.customers.update(externalId, body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.customers.update(externalId, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/discounts/create-discounts.ts b/packages/mcp-server/src/tools/discounts/create-discounts.ts index 2dcb150a..3190fe7b 100644 --- a/packages/mcp-server/src/tools/discounts/create-discounts.ts +++ b/packages/mcp-server/src/tools/discounts/create-discounts.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -136,7 +136,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/discounts/list-discounts.ts b/packages/mcp-server/src/tools/discounts/list-discounts.ts index 63e5e955..9f28a4d3 100644 --- a/packages/mcp-server/src/tools/discounts/list-discounts.ts +++ b/packages/mcp-server/src/tools/discounts/list-discounts.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/discounts/retrieve-discounts.ts b/packages/mcp-server/src/tools/discounts/retrieve-discounts.ts index 027e18ac..ee0ac925 100644 --- a/packages/mcp-server/src/tools/discounts/retrieve-discounts.ts +++ b/packages/mcp-server/src/tools/discounts/retrieve-discounts.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.retrieve(id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/discounts/update-discounts.ts b/packages/mcp-server/src/tools/discounts/update-discounts.ts index b4e36f69..668a80e2 100644 --- a/packages/mcp-server/src/tools/discounts/update-discounts.ts +++ b/packages/mcp-server/src/tools/discounts/update-discounts.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -150,7 +150,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.update(id, body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.update(id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/invoice-line-items/list-invoice-line-items.ts b/packages/mcp-server/src/tools/invoice-line-items/list-invoice-line-items.ts index 011b6e7b..ea95c83a 100644 --- a/packages/mcp-server/src/tools/invoice-line-items/list-invoice-line-items.ts +++ b/packages/mcp-server/src/tools/invoice-line-items/list-invoice-line-items.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.invoiceLineItems.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.invoiceLineItems.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/invoice-line-items/retrieve-invoice-line-items.ts b/packages/mcp-server/src/tools/invoice-line-items/retrieve-invoice-line-items.ts index 24e36992..44f7f64e 100644 --- a/packages/mcp-server/src/tools/invoice-line-items/retrieve-invoice-line-items.ts +++ b/packages/mcp-server/src/tools/invoice-line-items/retrieve-invoice-line-items.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.invoiceLineItems.retrieve(id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.invoiceLineItems.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/payment-methods/list-payment-methods.ts b/packages/mcp-server/src/tools/payment-methods/list-payment-methods.ts index 50cf3179..01d18a9b 100644 --- a/packages/mcp-server/src/tools/payment-methods/list-payment-methods.ts +++ b/packages/mcp-server/src/tools/payment-methods/list-payment-methods.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.paymentMethods.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.paymentMethods.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/payment-methods/retrieve-payment-methods.ts b/packages/mcp-server/src/tools/payment-methods/retrieve-payment-methods.ts index 603e871f..e20b1124 100644 --- a/packages/mcp-server/src/tools/payment-methods/retrieve-payment-methods.ts +++ b/packages/mcp-server/src/tools/payment-methods/retrieve-payment-methods.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.paymentMethods.retrieve(id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.paymentMethods.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/pricing-models/create-pricing-models.ts b/packages/mcp-server/src/tools/pricing-models/create-pricing-models.ts index b350151e..4b37a1e6 100644 --- a/packages/mcp-server/src/tools/pricing-models/create-pricing-models.ts +++ b/packages/mcp-server/src/tools/pricing-models/create-pricing-models.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'create_pricing_models', description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCreate Pricing Model\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/pricing_model_create_response',\n $defs: {\n pricing_model_create_response: {\n type: 'object',\n properties: {\n pricingModel: {\n $ref: '#/$defs/pricing_model_client_select_schema'\n }\n },\n required: [ 'pricingModel'\n ]\n },\n pricing_model_client_select_schema: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n isDefault: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n }\n },\n required: [ 'id',\n 'createdAt',\n 'isDefault',\n 'livemode',\n 'name',\n 'organizationId',\n 'updatedAt'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCreate Pricing Model\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/pricing_model_create_response',\n $defs: {\n pricing_model_create_response: {\n type: 'object',\n properties: {\n pricingModel: {\n $ref: '#/$defs/pricing_model_client_select_schema'\n }\n },\n required: [ 'pricingModel'\n ]\n },\n pricing_model_client_select_schema: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n integrationGuideHash: {\n type: 'string'\n },\n isDefault: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n }\n },\n required: [ 'id',\n 'createdAt',\n 'integrationGuideHash',\n 'isDefault',\n 'livemode',\n 'name',\n 'organizationId',\n 'updatedAt'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -28,6 +28,9 @@ export const tool: Tool = { name: { type: 'string', }, + integrationGuideHash: { + type: 'string', + }, isDefault: { type: 'boolean', }, @@ -52,7 +55,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.pricingModels.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.pricingModels.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/pricing-models/list-pricing-models.ts b/packages/mcp-server/src/tools/pricing-models/list-pricing-models.ts index ceaf4d47..f05dde79 100644 --- a/packages/mcp-server/src/tools/pricing-models/list-pricing-models.ts +++ b/packages/mcp-server/src/tools/pricing-models/list-pricing-models.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'list_pricing_models', description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nList Pricing Models\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/pricing_model_list_response',\n $defs: {\n pricing_model_list_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/pricing_model_client_select_schema'\n }\n },\n hasMore: {\n type: 'boolean'\n },\n total: {\n type: 'number'\n },\n currentCursor: {\n type: 'string'\n },\n nextCursor: {\n type: 'string'\n }\n },\n required: [ 'data',\n 'hasMore',\n 'total'\n ]\n },\n pricing_model_client_select_schema: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n isDefault: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n }\n },\n required: [ 'id',\n 'createdAt',\n 'isDefault',\n 'livemode',\n 'name',\n 'organizationId',\n 'updatedAt'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nList Pricing Models\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/pricing_model_list_response',\n $defs: {\n pricing_model_list_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/pricing_model_client_select_schema'\n }\n },\n hasMore: {\n type: 'boolean'\n },\n total: {\n type: 'number'\n },\n currentCursor: {\n type: 'string'\n },\n nextCursor: {\n type: 'string'\n }\n },\n required: [ 'data',\n 'hasMore',\n 'total'\n ]\n },\n pricing_model_client_select_schema: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n integrationGuideHash: {\n type: 'string'\n },\n isDefault: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n }\n },\n required: [ 'id',\n 'createdAt',\n 'integrationGuideHash',\n 'isDefault',\n 'livemode',\n 'name',\n 'organizationId',\n 'updatedAt'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.pricingModels.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.pricingModels.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/pricing-models/update-pricing-models.ts b/packages/mcp-server/src/tools/pricing-models/update-pricing-models.ts index 50e3ceaf..53e05d5b 100644 --- a/packages/mcp-server/src/tools/pricing-models/update-pricing-models.ts +++ b/packages/mcp-server/src/tools/pricing-models/update-pricing-models.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'update_pricing_models', description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpdate Pricing Model\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/pricing_model_update_response',\n $defs: {\n pricing_model_update_response: {\n type: 'object',\n properties: {\n pricingModel: {\n $ref: '#/$defs/pricing_model_client_select_schema'\n }\n },\n required: [ 'pricingModel'\n ]\n },\n pricing_model_client_select_schema: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n isDefault: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n }\n },\n required: [ 'id',\n 'createdAt',\n 'isDefault',\n 'livemode',\n 'name',\n 'organizationId',\n 'updatedAt'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nUpdate Pricing Model\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/pricing_model_update_response',\n $defs: {\n pricing_model_update_response: {\n type: 'object',\n properties: {\n pricingModel: {\n $ref: '#/$defs/pricing_model_client_select_schema'\n }\n },\n required: [ 'pricingModel'\n ]\n },\n pricing_model_client_select_schema: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n integrationGuideHash: {\n type: 'string'\n },\n isDefault: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n }\n },\n required: [ 'id',\n 'createdAt',\n 'integrationGuideHash',\n 'isDefault',\n 'livemode',\n 'name',\n 'organizationId',\n 'updatedAt'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -31,6 +31,9 @@ export const tool: Tool = { id: { type: 'string', }, + integrationGuideHash: { + type: 'string', + }, isDefault: { type: 'boolean', }, @@ -56,7 +59,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.pricingModels.update(id, body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.pricingModels.update(id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/products/create-products.ts b/packages/mcp-server/src/tools/products/create-products.ts index 096fbee1..a3c1ad73 100644 --- a/packages/mcp-server/src/tools/products/create-products.ts +++ b/packages/mcp-server/src/tools/products/create-products.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -234,7 +234,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.products.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.products.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/products/list-products.ts b/packages/mcp-server/src/tools/products/list-products.ts index 7c0988cd..50fe7d4d 100644 --- a/packages/mcp-server/src/tools/products/list-products.ts +++ b/packages/mcp-server/src/tools/products/list-products.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.products.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.products.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/products/update-products.ts b/packages/mcp-server/src/tools/products/update-products.ts index a08de7df..b4250ec7 100644 --- a/packages/mcp-server/src/tools/products/update-products.ts +++ b/packages/mcp-server/src/tools/products/update-products.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -69,17 +69,30 @@ export const tool: Tool = { { type: 'object', properties: { - id: { + intervalCount: { + type: 'integer', + description: 'A positive integer', + }, + intervalUnit: { type: 'string', + enum: ['day', 'week', 'month', 'year'], }, isDefault: { type: 'boolean', description: 'Whether or not this price is the default price for the product.', }, + productId: { + type: 'string', + }, type: { type: 'string', enum: ['subscription'], }, + unitPrice: { + type: 'number', + description: + 'The price per unit. This should be in the smallest unit of the currency. For example, if the currency is USD, GBP, CAD, EUR or SGD, the price should be in cents.', + }, active: { type: 'boolean', }, @@ -89,49 +102,110 @@ export const tool: Tool = { slug: { type: 'string', }, + trialPeriodDays: { + type: 'number', + description: + 'The trial period in days. If the trial period is 0 or null, there will be no trial period.', + }, + usageEventsPerUnit: { + type: 'null', + description: 'Omitted.', + }, + usageMeterId: { + type: 'null', + description: 'Omitted.', + }, }, - required: ['id', 'isDefault', 'type'], + required: ['intervalCount', 'intervalUnit', 'isDefault', 'productId', 'type', 'unitPrice'], }, { type: 'object', properties: { - id: { - type: 'string', - }, isDefault: { type: 'boolean', description: 'Whether or not this price is the default price for the product.', }, + productId: { + type: 'string', + }, type: { type: 'string', enum: ['single_payment'], }, + unitPrice: { + type: 'number', + description: + 'The price per unit. This should be in the smallest unit of the currency. For example, if the currency is USD, GBP, CAD, EUR or SGD, the price should be in cents.', + }, active: { type: 'boolean', }, + intervalCount: { + type: 'null', + description: 'Omitted.', + }, + intervalUnit: { + type: 'null', + description: 'Omitted.', + }, name: { type: 'string', }, slug: { type: 'string', }, + trialPeriodDays: { + type: 'null', + description: 'Omitted.', + }, + usageEventsPerUnit: { + type: 'null', + description: 'Omitted.', + }, + usageMeterId: { + type: 'null', + description: 'Omitted.', + }, }, - required: ['id', 'isDefault', 'type'], + required: ['isDefault', 'productId', 'type', 'unitPrice'], }, { type: 'object', properties: { - id: { + intervalCount: { + type: 'integer', + description: 'A positive integer', + }, + intervalUnit: { type: 'string', + enum: ['day', 'week', 'month', 'year'], }, isDefault: { type: 'boolean', description: 'Whether or not this price is the default price for the product.', }, + productId: { + type: 'string', + }, type: { type: 'string', enum: ['usage'], }, + unitPrice: { + type: 'number', + description: + 'The price per unit. This should be in the smallest unit of the currency. For example, if the currency is USD, GBP, CAD, EUR or SGD, the price should be in cents.', + }, + usageEventsPerUnit: { + type: 'integer', + description: + 'The number of usage events per unit. Used to determine how to map usage events to quantities when raising invoices for usage.', + }, + usageMeterId: { + type: 'string', + description: + 'The usage meter that uses this price. All usage events on that meter must be associated with a price that is also associated with that usage meter.', + }, active: { type: 'boolean', }, @@ -141,8 +215,21 @@ export const tool: Tool = { slug: { type: 'string', }, + trialPeriodDays: { + type: 'null', + description: 'Omitted.', + }, }, - required: ['id', 'isDefault', 'type'], + required: [ + 'intervalCount', + 'intervalUnit', + 'isDefault', + 'productId', + 'type', + 'unitPrice', + 'usageEventsPerUnit', + 'usageMeterId', + ], }, ], }, @@ -162,7 +249,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.products.update(id, body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.products.update(id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/subscriptions/cancel-subscriptions.ts b/packages/mcp-server/src/tools/subscriptions/cancel-subscriptions.ts index 5493e826..9a9bd4ca 100644 --- a/packages/mcp-server/src/tools/subscriptions/cancel-subscriptions.ts +++ b/packages/mcp-server/src/tools/subscriptions/cancel-subscriptions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -18,7 +18,7 @@ export const metadata: Metadata = { export const tool: Tool = { name: 'cancel_subscriptions', description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCancel a Subscription\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/subscription_cancel_response',\n $defs: {\n subscription_cancel_response: {\n type: 'object',\n properties: {\n subscription: {\n anyOf: [ {\n $ref: '#/$defs/standard_subscription_record'\n },\n {\n $ref: '#/$defs/non_renewing_subscription_record'\n }\n ]\n }\n },\n required: [ 'subscription'\n ]\n },\n standard_subscription_record: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n backupPaymentMethodId: {\n type: 'string'\n },\n cancellationReason: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n current: {\n type: 'boolean',\n description: 'Whether the subscription is current (statuses \"active\", \"trialing\", \"past_due\", or \"cancellation_scheduled\")'\n },\n customerId: {\n type: 'string'\n },\n defaultPaymentMethodId: {\n type: 'string'\n },\n interval: {\n type: 'string',\n enum: [ 'day',\n 'week',\n 'month',\n 'year'\n ]\n },\n intervalCount: {\n type: 'integer',\n description: 'A positive integer'\n },\n isFreePlan: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n priceId: {\n type: 'string'\n },\n renews: {\n type: 'string',\n enum: [ true\n ]\n },\n replacedBySubscriptionId: {\n type: 'string'\n },\n runBillingAtPeriodStart: {\n type: 'boolean'\n },\n startDate: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n status: {\n type: 'string',\n enum: [ 'trialing',\n 'active',\n 'past_due',\n 'unpaid',\n 'cancellation_scheduled',\n 'incomplete',\n 'incomplete_expired',\n 'canceled',\n 'paused'\n ]\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n billingCycleAnchorDate: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n canceledAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n cancelScheduledAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n currentBillingPeriodEnd: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n currentBillingPeriodStart: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n metadata: {\n type: 'object',\n description: 'JSON object',\n additionalProperties: true\n },\n trialEnd: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n }\n },\n required: [ 'id',\n 'backupPaymentMethodId',\n 'cancellationReason',\n 'createdAt',\n 'current',\n 'customerId',\n 'defaultPaymentMethodId',\n 'interval',\n 'intervalCount',\n 'isFreePlan',\n 'livemode',\n 'name',\n 'organizationId',\n 'priceId',\n 'renews',\n 'replacedBySubscriptionId',\n 'runBillingAtPeriodStart',\n 'startDate',\n 'status',\n 'updatedAt'\n ]\n },\n non_renewing_subscription_record: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n backupPaymentMethodId: {\n type: 'string'\n },\n billingCycleAnchorDate: {\n type: 'null',\n description: 'Omitted.'\n },\n cancellationReason: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n current: {\n type: 'boolean',\n description: 'Whether the subscription is current (statuses \"active\", \"trialing\", \"past_due\", \"cancellation_scheduled\", or \"credit_trial\")'\n },\n currentBillingPeriodEnd: {\n type: 'null',\n description: 'Omitted.'\n },\n currentBillingPeriodStart: {\n type: 'null',\n description: 'Omitted.'\n },\n customerId: {\n type: 'string'\n },\n defaultPaymentMethodId: {\n type: 'string'\n },\n interval: {\n type: 'null',\n description: 'Omitted.'\n },\n intervalCount: {\n type: 'null',\n description: 'Omitted.'\n },\n isFreePlan: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n priceId: {\n type: 'string'\n },\n renews: {\n type: 'boolean'\n },\n replacedBySubscriptionId: {\n type: 'string'\n },\n runBillingAtPeriodStart: {\n type: 'boolean'\n },\n startDate: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n status: {\n type: 'string',\n enum: [ 'active',\n 'canceled',\n 'credit_trial'\n ]\n },\n trialEnd: {\n type: 'null',\n description: 'Omitted.'\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n canceledAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n cancelScheduledAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n metadata: {\n type: 'object',\n description: 'JSON object',\n additionalProperties: true\n }\n },\n required: [ 'id',\n 'backupPaymentMethodId',\n 'billingCycleAnchorDate',\n 'cancellationReason',\n 'createdAt',\n 'current',\n 'currentBillingPeriodEnd',\n 'currentBillingPeriodStart',\n 'customerId',\n 'defaultPaymentMethodId',\n 'interval',\n 'intervalCount',\n 'isFreePlan',\n 'livemode',\n 'name',\n 'organizationId',\n 'priceId',\n 'renews',\n 'replacedBySubscriptionId',\n 'runBillingAtPeriodStart',\n 'startDate',\n 'status',\n 'trialEnd',\n 'updatedAt'\n ]\n }\n }\n}\n```", + "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nCancel Subscription\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/subscription_cancel_response',\n $defs: {\n subscription_cancel_response: {\n type: 'object',\n properties: {\n subscription: {\n anyOf: [ {\n $ref: '#/$defs/standard_subscription_record'\n },\n {\n $ref: '#/$defs/non_renewing_subscription_record'\n }\n ]\n }\n },\n required: [ 'subscription'\n ]\n },\n standard_subscription_record: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n backupPaymentMethodId: {\n type: 'string'\n },\n cancellationReason: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n current: {\n type: 'boolean',\n description: 'Whether the subscription is current (statuses \"active\", \"trialing\", \"past_due\", or \"cancellation_scheduled\")'\n },\n customerId: {\n type: 'string'\n },\n defaultPaymentMethodId: {\n type: 'string'\n },\n interval: {\n type: 'string',\n enum: [ 'day',\n 'week',\n 'month',\n 'year'\n ]\n },\n intervalCount: {\n type: 'integer',\n description: 'A positive integer'\n },\n isFreePlan: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n priceId: {\n type: 'string'\n },\n renews: {\n type: 'string',\n enum: [ true\n ]\n },\n replacedBySubscriptionId: {\n type: 'string'\n },\n runBillingAtPeriodStart: {\n type: 'boolean'\n },\n startDate: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n status: {\n type: 'string',\n enum: [ 'trialing',\n 'active',\n 'past_due',\n 'unpaid',\n 'cancellation_scheduled',\n 'incomplete',\n 'incomplete_expired',\n 'canceled',\n 'paused'\n ]\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n billingCycleAnchorDate: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n canceledAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n cancelScheduledAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n currentBillingPeriodEnd: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n currentBillingPeriodStart: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n metadata: {\n type: 'object',\n description: 'JSON object',\n additionalProperties: true\n },\n trialEnd: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n }\n },\n required: [ 'id',\n 'backupPaymentMethodId',\n 'cancellationReason',\n 'createdAt',\n 'current',\n 'customerId',\n 'defaultPaymentMethodId',\n 'interval',\n 'intervalCount',\n 'isFreePlan',\n 'livemode',\n 'name',\n 'organizationId',\n 'priceId',\n 'renews',\n 'replacedBySubscriptionId',\n 'runBillingAtPeriodStart',\n 'startDate',\n 'status',\n 'updatedAt'\n ]\n },\n non_renewing_subscription_record: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n backupPaymentMethodId: {\n type: 'string'\n },\n billingCycleAnchorDate: {\n type: 'null',\n description: 'Omitted.'\n },\n cancellationReason: {\n type: 'string'\n },\n createdAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n current: {\n type: 'boolean',\n description: 'Whether the subscription is current (statuses \"active\", \"trialing\", \"past_due\", \"cancellation_scheduled\", or \"credit_trial\")'\n },\n currentBillingPeriodEnd: {\n type: 'null',\n description: 'Omitted.'\n },\n currentBillingPeriodStart: {\n type: 'null',\n description: 'Omitted.'\n },\n customerId: {\n type: 'string'\n },\n defaultPaymentMethodId: {\n type: 'string'\n },\n interval: {\n type: 'null',\n description: 'Omitted.'\n },\n intervalCount: {\n type: 'null',\n description: 'Omitted.'\n },\n isFreePlan: {\n type: 'boolean'\n },\n livemode: {\n type: 'boolean'\n },\n name: {\n type: 'string'\n },\n organizationId: {\n type: 'string'\n },\n priceId: {\n type: 'string'\n },\n renews: {\n type: 'boolean'\n },\n replacedBySubscriptionId: {\n type: 'string'\n },\n runBillingAtPeriodStart: {\n type: 'boolean'\n },\n startDate: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n status: {\n type: 'string',\n enum: [ 'active',\n 'canceled',\n 'credit_trial'\n ]\n },\n trialEnd: {\n type: 'null',\n description: 'Omitted.'\n },\n updatedAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n canceledAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n cancelScheduledAt: {\n type: 'integer',\n description: 'Epoch milliseconds.'\n },\n metadata: {\n type: 'object',\n description: 'JSON object',\n additionalProperties: true\n }\n },\n required: [ 'id',\n 'backupPaymentMethodId',\n 'billingCycleAnchorDate',\n 'cancellationReason',\n 'createdAt',\n 'current',\n 'currentBillingPeriodEnd',\n 'currentBillingPeriodStart',\n 'customerId',\n 'defaultPaymentMethodId',\n 'interval',\n 'intervalCount',\n 'isFreePlan',\n 'livemode',\n 'name',\n 'organizationId',\n 'priceId',\n 'renews',\n 'replacedBySubscriptionId',\n 'runBillingAtPeriodStart',\n 'startDate',\n 'status',\n 'trialEnd',\n 'updatedAt'\n ]\n }\n }\n}\n```", inputSchema: { type: 'object', properties: { @@ -77,7 +77,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.subscriptions.cancel(id, body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.subscriptions.cancel(id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/subscriptions/create-subscriptions.ts b/packages/mcp-server/src/tools/subscriptions/create-subscriptions.ts index 09efdbbd..63e3d48d 100644 --- a/packages/mcp-server/src/tools/subscriptions/create-subscriptions.ts +++ b/packages/mcp-server/src/tools/subscriptions/create-subscriptions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -22,19 +22,18 @@ export const tool: Tool = { inputSchema: { type: 'object', properties: { - customerId: { + backupPaymentMethodId: { type: 'string', - description: 'The customer for the subscription.', + description: + 'The payment method to try if charges for the subscription fail with the default payment method.', }, - priceId: { + customerExternalId: { type: 'string', - description: - 'The price to subscribe to. Used to determine whether the subscription is usage-based or not, and set other defaults such as trial period and billing intervals.', + description: 'The external ID of the customer. If not provided, customerId is required.', }, - backupPaymentMethodId: { + customerId: { type: 'string', - description: - 'The payment method to try if charges for the subscription fail with the default payment method.', + description: 'The internal ID of the customer. If not provided, customerExternalId is required.', }, defaultPaymentMethodId: { type: 'string', @@ -44,7 +43,7 @@ export const tool: Tool = { interval: { type: 'string', description: - 'The interval of the subscription. If not provided, defaults to the interval of the price provided by `priceId`.', + 'The interval of the subscription. If not provided, defaults to the interval of the price provided by `priceId` or `priceSlug`.', enum: ['day', 'week', 'month', 'year'], }, intervalCount: { @@ -60,7 +59,17 @@ export const tool: Tool = { name: { type: 'string', description: - "The name of the subscription. If not provided, defaults to the name of the product associated with the price provided by 'priceId'.", + "The name of the subscription. If not provided, defaults to the name of the product associated with the price provided by 'priceId' or 'priceSlug'.", + }, + priceId: { + type: 'string', + description: + 'The id of the price to subscribe to. If not provided, priceSlug is required. Used to determine whether the subscription is usage-based or not, and set other defaults such as trial period and billing intervals.', + }, + priceSlug: { + type: 'string', + description: + "The slug of the price to subscribe to. If not provided, priceId is required. Price slugs are scoped to the customer's pricing model. Used to determine whether the subscription is usage-based or not, and set other defaults such as trial period and billing intervals.", }, quantity: { type: 'number', @@ -82,14 +91,21 @@ export const tool: Tool = { 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', }, }, - required: ['customerId', 'priceId'], + required: [], }, annotations: {}, }; export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.subscriptions.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.subscriptions.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/subscriptions/list-subscriptions.ts b/packages/mcp-server/src/tools/subscriptions/list-subscriptions.ts index 90419c0d..60479856 100644 --- a/packages/mcp-server/src/tools/subscriptions/list-subscriptions.ts +++ b/packages/mcp-server/src/tools/subscriptions/list-subscriptions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.subscriptions.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.subscriptions.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/subscriptions/retrieve-subscriptions.ts b/packages/mcp-server/src/tools/subscriptions/retrieve-subscriptions.ts index 9d7d9d99..d10cea22 100644 --- a/packages/mcp-server/src/tools/subscriptions/retrieve-subscriptions.ts +++ b/packages/mcp-server/src/tools/subscriptions/retrieve-subscriptions.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.subscriptions.retrieve(id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.subscriptions.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/tools/types.ts index 3c3416f0..7aea43d2 100644 --- a/packages/mcp-server/src/tools/types.ts +++ b/packages/mcp-server/src/tools/types.ts @@ -87,6 +87,18 @@ export async function asBinaryContentResult(response: Response): Promise | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.usageEvents.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.usageEvents.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/usage-events/retrieve-usage-events.ts b/packages/mcp-server/src/tools/usage-events/retrieve-usage-events.ts index 9e9812ff..68689a2a 100644 --- a/packages/mcp-server/src/tools/usage-events/retrieve-usage-events.ts +++ b/packages/mcp-server/src/tools/usage-events/retrieve-usage-events.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.usageEvents.retrieve(id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.usageEvents.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/usage-meters/create-usage-meters.ts b/packages/mcp-server/src/tools/usage-meters/create-usage-meters.ts index e2043453..2b848a6a 100644 --- a/packages/mcp-server/src/tools/usage-meters/create-usage-meters.ts +++ b/packages/mcp-server/src/tools/usage-meters/create-usage-meters.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -43,6 +43,21 @@ export const tool: Tool = { }, required: ['name', 'pricingModelId', 'slug'], }, + price: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['single_payment', 'subscription', 'usage'], + }, + unitPrice: { + type: 'number', + }, + usageEventsPerUnit: { + type: 'number', + }, + }, + }, jq_filter: { type: 'string', title: 'jq Filter', @@ -57,7 +72,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.usageMeters.create(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.usageMeters.create(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/usage-meters/list-usage-meters.ts b/packages/mcp-server/src/tools/usage-meters/list-usage-meters.ts index 61828016..afafeb06 100644 --- a/packages/mcp-server/src/tools/usage-meters/list-usage-meters.ts +++ b/packages/mcp-server/src/tools/usage-meters/list-usage-meters.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -44,7 +44,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.usageMeters.list(body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.usageMeters.list(body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/usage-meters/retrieve-usage-meters.ts b/packages/mcp-server/src/tools/usage-meters/retrieve-usage-meters.ts index 742341ce..3b1c0c6e 100644 --- a/packages/mcp-server/src/tools/usage-meters/retrieve-usage-meters.ts +++ b/packages/mcp-server/src/tools/usage-meters/retrieve-usage-meters.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -41,7 +41,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.usageMeters.retrieve(id))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.usageMeters.retrieve(id))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/usage-meters/update-usage-meters.ts b/packages/mcp-server/src/tools/usage-meters/update-usage-meters.ts index e8b9ba82..549c48c8 100644 --- a/packages/mcp-server/src/tools/usage-meters/update-usage-meters.ts +++ b/packages/mcp-server/src/tools/usage-meters/update-usage-meters.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { maybeFilter } from '@flowglad/node-mcp/filtering'; -import { Metadata, asTextContentResult } from '@flowglad/node-mcp/tools/types'; +import { isJqError, maybeFilter } from '@flowglad/node-mcp/filtering'; +import { Metadata, asErrorResult, asTextContentResult } from '@flowglad/node-mcp/tools/types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import Flowglad from '@flowglad/node'; @@ -62,7 +62,14 @@ export const tool: Tool = { export const handler = async (client: Flowglad, args: Record | undefined) => { const { id, jq_filter, ...body } = args as any; - return asTextContentResult(await maybeFilter(jq_filter, await client.usageMeters.update(id, body))); + try { + return asTextContentResult(await maybeFilter(jq_filter, await client.usageMeters.update(id, body))); + } catch (error) { + if (isJqError(error)) { + return asErrorResult(error.message); + } + throw error; + } }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts index a8a5b81a..4d9b60ca 100644 --- a/packages/mcp-server/tests/options.test.ts +++ b/packages/mcp-server/tests/options.test.ts @@ -171,6 +171,7 @@ describe('parseQueryOptions', () => { const defaultOptions = { client: undefined, includeDynamicTools: undefined, + includeCodeTools: undefined, includeAllTools: undefined, filters: [], capabilities: { @@ -383,6 +384,27 @@ describe('parseQueryOptions', () => { { type: 'tool', op: 'exclude', value: 'exclude-tool' }, ]); }); + + it('code tools are enabled on http servers with default option set', () => { + const query = 'tools=code'; + const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: true }, query); + + expect(result.includeCodeTools).toBe(true); + }); + + it('code tools are prevented on http servers when no default option set', () => { + const query = 'tools=code'; + const result = parseQueryOptions(defaultOptions, query); + + expect(result.includeCodeTools).toBe(undefined); + }); + + it('code tools are prevented on http servers when default option is explicitly false', () => { + const query = 'tools=code'; + const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: false }, query); + + expect(result.includeCodeTools).toBe(false); + }); }); describe('parseEmbeddedJSON', () => { diff --git a/packages/mcp-server/yarn.lock b/packages/mcp-server/yarn.lock index 25cc0cde..e8dc585e 100644 --- a/packages/mcp-server/yarn.lock +++ b/packages/mcp-server/yarn.lock @@ -2735,9 +2735,9 @@ jest@^29.4.0: import-local "^3.0.2" jest-cli "^29.7.0" -"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz": - version "0.8.6" - resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz#14d0e126987736e82e964d675c3838b5944faa6f" +"jq-web@https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz": + version "0.8.8" + resolved "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz#7849ef64bdfc28f70cbfc9888f886860e96da10d" js-tokens@^4.0.0: version "4.0.0" diff --git a/src/client.ts b/src/client.ts index d9d1b6a3..8a7832c5 100644 --- a/src/client.ts +++ b/src/client.ts @@ -41,7 +41,7 @@ import { CustomerUpdateResponse, Customers, ToggleSubscriptionItemFeatureRecord, - UsageCreditGrantSubscriptionItemFeatureClientSelectSchema, + UsageCreditGrantSubscriptionItemFeatureRecord, } from './resources/customers'; import { DefaultDiscountClientSelectSchema, @@ -963,7 +963,7 @@ export declare namespace Flowglad { Customers as Customers, type CustomerClientSelectSchema as CustomerClientSelectSchema, type ToggleSubscriptionItemFeatureRecord as ToggleSubscriptionItemFeatureRecord, - type UsageCreditGrantSubscriptionItemFeatureClientSelectSchema as UsageCreditGrantSubscriptionItemFeatureClientSelectSchema, + type UsageCreditGrantSubscriptionItemFeatureRecord as UsageCreditGrantSubscriptionItemFeatureRecord, type CustomerCreateResponse as CustomerCreateResponse, type CustomerRetrieveResponse as CustomerRetrieveResponse, type CustomerUpdateResponse as CustomerUpdateResponse, diff --git a/src/resources/checkout-sessions.ts b/src/resources/checkout-sessions.ts index c4868864..3f77a878 100644 --- a/src/resources/checkout-sessions.ts +++ b/src/resources/checkout-sessions.ts @@ -503,15 +503,22 @@ export namespace CheckoutSessionCreateParams { preserveBillingCycleAnchor?: boolean; /** - * The quantity of the purchase or subscription created when this checkout session - * succeeds. Ignored if the checkout session is of type `invoice`. If not provided, defaults to 1. + * The ID of the price the customer shall purchase. If not provided, priceSlug is + * required. */ - quantity?: number; + priceId?: string; + + /** + * The slug of the price the customer shall purchase. If not provided, priceId is + * required. + */ + priceSlug?: string; /** - * The ID of the price the customer shall purchase. + * The quantity of the purchase or subscription created when this checkout session + * succeeds. Ignored if the checkout session is of type `invoice`. If not provided, defaults to 1. */ - priceId: string; + quantity?: number; } export interface AnonymousProductCheckoutSessionInput { @@ -522,11 +529,6 @@ export namespace CheckoutSessionCreateParams { */ cancelUrl: string; - /** - * The ID of the price the customer shall purchase - */ - priceId: string; - /** * The URL to redirect to after the purchase is successful */ @@ -554,6 +556,18 @@ export namespace CheckoutSessionCreateParams { */ preserveBillingCycleAnchor?: boolean; + /** + * The ID of the price the customer shall purchase. If not provided, priceSlug is + * required. + */ + priceId?: string; + + /** + * The slug of the price the customer shall purchase from the organization's + * default pricing model. If not provided, priceId is required. + */ + priceSlug?: string; + /** * The quantity of the purchase or subscription created when this checkout session * succeeds. Ignored if the checkout session is of type `invoice`. @@ -572,8 +586,6 @@ export namespace CheckoutSessionCreateParams { */ customerExternalId: string; - priceId: string; - /** * The URL to redirect to after the purchase is successful */ diff --git a/src/resources/customers.ts b/src/resources/customers.ts index 11f49db7..d0065d3b 100644 --- a/src/resources/customers.ts +++ b/src/resources/customers.ts @@ -138,7 +138,7 @@ export interface ToggleSubscriptionItemFeatureRecord { usageMeterId?: null; } -export interface UsageCreditGrantSubscriptionItemFeatureClientSelectSchema { +export interface UsageCreditGrantSubscriptionItemFeatureRecord { id: string; amount: number; @@ -152,8 +152,12 @@ export interface UsageCreditGrantSubscriptionItemFeatureClientSelectSchema { livemode: boolean; + name: string; + renewalFrequency: 'once' | 'every_billing_period'; + slug: string; + subscriptionItemId: string; type: 'usage_credit_grant'; @@ -293,6 +297,15 @@ export interface CustomerRetrieveBillingResponse { | CustomerRetrieveBillingResponse.StandardSubscriptionDetails >; + /** + * The most recently created current subscription for the customer. If createdAt + * timestamps tie, the most recently updated subscription will be returned. If + * updatedAt also ties, subscription id is used as the final tiebreaker. + */ + currentSubscription?: + | CustomerRetrieveBillingResponse.NonRenewingSubscriptionDetails + | CustomerRetrieveBillingResponse.StandardSubscriptionDetails; + /** * The current subscriptions for the customer. By default, customers can only have * one active subscription at a time. This will only return multiple subscriptions @@ -740,7 +753,7 @@ export namespace CustomerRetrieveBillingResponse { export interface Experimental { featureItems: Array< | CustomersAPI.ToggleSubscriptionItemFeatureRecord - | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureClientSelectSchema + | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureRecord >; usageMeterBalances: Array; @@ -956,7 +969,7 @@ export namespace CustomerRetrieveBillingResponse { export interface Experimental { featureItems: Array< | CustomersAPI.ToggleSubscriptionItemFeatureRecord - | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureClientSelectSchema + | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureRecord >; usageMeterBalances: Array; @@ -1166,13 +1179,59 @@ export namespace CustomerRetrieveBillingResponse { export interface Experimental { featureItems: Array< | CustomersAPI.ToggleSubscriptionItemFeatureRecord - | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureClientSelectSchema + | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureRecord >; usageMeterBalances: Array; } export namespace Experimental { + export interface UsageCreditGrantSubscriptionItemFeatureRecord { + id: string; + + amount: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + featureId: string; + + livemode: boolean; + + name: string; + + renewalFrequency: 'once' | 'every_billing_period'; + + slug: string; + + subscriptionItemId: string; + + type: 'usage_credit_grant'; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + usageMeterId: string; + + /** + * Epoch milliseconds. + */ + detachedAt?: number | null; + + detachedReason?: string | null; + + /** + * Epoch milliseconds. + */ + expiredAt?: number | null; + + productFeatureId?: string | null; + } + /** * A usage meter and the available balance for that meter, scoped to a given * subscription. @@ -1382,13 +1441,1095 @@ export namespace CustomerRetrieveBillingResponse { export interface Experimental { featureItems: Array< | CustomersAPI.ToggleSubscriptionItemFeatureRecord - | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureClientSelectSchema + | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureRecord >; usageMeterBalances: Array; } export namespace Experimental { + export interface UsageCreditGrantSubscriptionItemFeatureRecord { + id: string; + + amount: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + featureId: string; + + livemode: boolean; + + name: string; + + renewalFrequency: 'once' | 'every_billing_period'; + + slug: string; + + subscriptionItemId: string; + + type: 'usage_credit_grant'; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + usageMeterId: string; + + /** + * Epoch milliseconds. + */ + detachedAt?: number | null; + + detachedReason?: string | null; + + /** + * Epoch milliseconds. + */ + expiredAt?: number | null; + + productFeatureId?: string | null; + } + + /** + * A usage meter and the available balance for that meter, scoped to a given + * subscription. + */ + export interface UsageMeterBalance { + id: string; + + /** + * The type of aggregation to perform on the usage meter. Defaults to "sum", which + * aggregates all the usage event amounts for the billing period. + * "count_distinct_properties" counts the number of distinct properties in the + * billing period for a given meter. + */ + aggregationType: 'sum' | 'count_distinct_properties'; + + availableBalance: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + livemode: boolean; + + name: string; + + organizationId: string; + + pricingModelId: string; + + slug: string; + + subscriptionId: string; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + } + } + } + + export interface NonRenewingSubscriptionDetails { + id: string; + + backupPaymentMethodId: string | null; + + /** + * Omitted. + */ + billingCycleAnchorDate: null; + + cancellationReason: string | null; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + current: boolean; + + /** + * Omitted. + */ + currentBillingPeriodEnd: null; + + /** + * Omitted. + */ + currentBillingPeriodStart: null; + + customerId: string; + + defaultPaymentMethodId: string | null; + + /** + * Omitted. + */ + interval: null; + + /** + * Omitted. + */ + intervalCount: null; + + isFreePlan: boolean | null; + + livemode: boolean; + + name: string | null; + + organizationId: string; + + priceId: string | null; + + renews: boolean; + + replacedBySubscriptionId: string | null; + + runBillingAtPeriodStart: boolean | null; + + /** + * Epoch milliseconds. + */ + startDate: number; + + status: 'active' | 'canceled' | 'credit_trial'; + + subscriptionItems: Array; + + /** + * Omitted. + */ + trialEnd: null; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + /** + * Epoch milliseconds. + */ + canceledAt?: number | null; + + /** + * Epoch milliseconds. + */ + cancelScheduledAt?: number | null; + + /** + * Experimental fields. May change without notice. + */ + experimental?: NonRenewingSubscriptionDetails.Experimental; + + /** + * JSON object + */ + metadata?: { [key: string]: string | number | boolean } | null; + } + + export namespace NonRenewingSubscriptionDetails { + export interface SubscriptionItem { + id: string; + + /** + * Epoch milliseconds. + */ + addedDate: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + externalId: string | null; + + livemode: boolean; + + name: string | null; + + price: + | PricesAPI.SubscriptionPriceClientSelectSchema + | PricesAPI.SinglePaymentPriceClientSelectSchema + | PricesAPI.UsagePriceClientSelectSchema; + + priceId: string; + + /** + * A positive integer + */ + quantity: number; + + subscriptionId: string; + + type: 'static'; + + unitPrice: number; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + /** + * Used as a flag to soft delete a subscription item without losing its history for + * auditability. If set, it will be removed from the subscription items list and + * will not be included in the billing period item list. Epoch milliseconds. + */ + expiredAt?: number | null; + + /** + * JSON object + */ + metadata?: { [key: string]: string | number | boolean } | null; + } + + /** + * Experimental fields. May change without notice. + */ + export interface Experimental { + featureItems: Array< + | CustomersAPI.ToggleSubscriptionItemFeatureRecord + | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureRecord + >; + + usageMeterBalances: Array; + } + + export namespace Experimental { + export interface UsageCreditGrantSubscriptionItemFeatureRecord { + id: string; + + amount: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + featureId: string; + + livemode: boolean; + + name: string; + + renewalFrequency: 'once' | 'every_billing_period'; + + slug: string; + + subscriptionItemId: string; + + type: 'usage_credit_grant'; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + usageMeterId: string; + + /** + * Epoch milliseconds. + */ + detachedAt?: number | null; + + detachedReason?: string | null; + + /** + * Epoch milliseconds. + */ + expiredAt?: number | null; + + productFeatureId?: string | null; + } + + /** + * A usage meter and the available balance for that meter, scoped to a given + * subscription. + */ + export interface UsageMeterBalance { + id: string; + + /** + * The type of aggregation to perform on the usage meter. Defaults to "sum", which + * aggregates all the usage event amounts for the billing period. + * "count_distinct_properties" counts the number of distinct properties in the + * billing period for a given meter. + */ + aggregationType: 'sum' | 'count_distinct_properties'; + + availableBalance: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + livemode: boolean; + + name: string; + + organizationId: string; + + pricingModelId: string; + + slug: string; + + subscriptionId: string; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + } + } + } + + export interface StandardSubscriptionDetails { + id: string; + + backupPaymentMethodId: string | null; + + cancellationReason: string | null; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + current: boolean; + + customerId: string; + + defaultPaymentMethodId: string | null; + + interval: 'day' | 'week' | 'month' | 'year'; + + /** + * A positive integer + */ + intervalCount: number; + + isFreePlan: boolean | null; + + livemode: boolean; + + name: string | null; + + organizationId: string; + + priceId: string | null; + + renews: true; + + replacedBySubscriptionId: string | null; + + runBillingAtPeriodStart: boolean | null; + + /** + * Epoch milliseconds. + */ + startDate: number; + + status: + | 'trialing' + | 'active' + | 'past_due' + | 'unpaid' + | 'cancellation_scheduled' + | 'incomplete' + | 'incomplete_expired' + | 'canceled' + | 'paused'; + + subscriptionItems: Array; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + /** + * Epoch milliseconds. + */ + billingCycleAnchorDate?: number | null; + + /** + * Epoch milliseconds. + */ + canceledAt?: number | null; + + /** + * Epoch milliseconds. + */ + cancelScheduledAt?: number | null; + + /** + * Epoch milliseconds. + */ + currentBillingPeriodEnd?: number | null; + + /** + * Epoch milliseconds. + */ + currentBillingPeriodStart?: number | null; + + /** + * Experimental fields. May change without notice. + */ + experimental?: StandardSubscriptionDetails.Experimental; + + /** + * JSON object + */ + metadata?: { [key: string]: string | number | boolean } | null; + + /** + * Epoch milliseconds. + */ + trialEnd?: number | null; + } + + export namespace StandardSubscriptionDetails { + export interface SubscriptionItem { + id: string; + + /** + * Epoch milliseconds. + */ + addedDate: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + externalId: string | null; + + livemode: boolean; + + name: string | null; + + price: + | PricesAPI.SubscriptionPriceClientSelectSchema + | PricesAPI.SinglePaymentPriceClientSelectSchema + | PricesAPI.UsagePriceClientSelectSchema; + + priceId: string; + + /** + * A positive integer + */ + quantity: number; + + subscriptionId: string; + + type: 'static'; + + unitPrice: number; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + /** + * Used as a flag to soft delete a subscription item without losing its history for + * auditability. If set, it will be removed from the subscription items list and + * will not be included in the billing period item list. Epoch milliseconds. + */ + expiredAt?: number | null; + + /** + * JSON object + */ + metadata?: { [key: string]: string | number | boolean } | null; + } + + /** + * Experimental fields. May change without notice. + */ + export interface Experimental { + featureItems: Array< + | CustomersAPI.ToggleSubscriptionItemFeatureRecord + | CustomersAPI.UsageCreditGrantSubscriptionItemFeatureRecord + >; + + usageMeterBalances: Array; + } + + export namespace Experimental { + export interface UsageCreditGrantSubscriptionItemFeatureRecord { + id: string; + + amount: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + featureId: string; + + livemode: boolean; + + name: string; + + renewalFrequency: 'once' | 'every_billing_period'; + + slug: string; + + subscriptionItemId: string; + + type: 'usage_credit_grant'; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + usageMeterId: string; + + /** + * Epoch milliseconds. + */ + detachedAt?: number | null; + + detachedReason?: string | null; + + /** + * Epoch milliseconds. + */ + expiredAt?: number | null; + + productFeatureId?: string | null; + } + + /** + * A usage meter and the available balance for that meter, scoped to a given + * subscription. + */ + export interface UsageMeterBalance { + id: string; + + /** + * The type of aggregation to perform on the usage meter. Defaults to "sum", which + * aggregates all the usage event amounts for the billing period. + * "count_distinct_properties" counts the number of distinct properties in the + * billing period for a given meter. + */ + aggregationType: 'sum' | 'count_distinct_properties'; + + availableBalance: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + livemode: boolean; + + name: string; + + organizationId: string; + + pricingModelId: string; + + slug: string; + + subscriptionId: string; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + } + } + } + + export interface NonRenewingSubscriptionDetails { + id: string; + + backupPaymentMethodId: string | null; + + /** + * Omitted. + */ + billingCycleAnchorDate: null; + + cancellationReason: string | null; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + current: boolean; + + /** + * Omitted. + */ + currentBillingPeriodEnd: null; + + /** + * Omitted. + */ + currentBillingPeriodStart: null; + + customerId: string; + + defaultPaymentMethodId: string | null; + + /** + * Omitted. + */ + interval: null; + + /** + * Omitted. + */ + intervalCount: null; + + isFreePlan: boolean | null; + + livemode: boolean; + + name: string | null; + + organizationId: string; + + priceId: string | null; + + renews: boolean; + + replacedBySubscriptionId: string | null; + + runBillingAtPeriodStart: boolean | null; + + /** + * Epoch milliseconds. + */ + startDate: number; + + status: 'active' | 'canceled' | 'credit_trial'; + + subscriptionItems: Array; + + /** + * Omitted. + */ + trialEnd: null; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + /** + * Epoch milliseconds. + */ + canceledAt?: number | null; + + /** + * Epoch milliseconds. + */ + cancelScheduledAt?: number | null; + + /** + * Experimental fields. May change without notice. + */ + experimental?: NonRenewingSubscriptionDetails.Experimental; + + /** + * JSON object + */ + metadata?: { [key: string]: string | number | boolean } | null; + } + + export namespace NonRenewingSubscriptionDetails { + export interface SubscriptionItem { + id: string; + + /** + * Epoch milliseconds. + */ + addedDate: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + externalId: string | null; + + livemode: boolean; + + name: string | null; + + price: + | PricesAPI.SubscriptionPriceClientSelectSchema + | PricesAPI.SinglePaymentPriceClientSelectSchema + | PricesAPI.UsagePriceClientSelectSchema; + + priceId: string; + + /** + * A positive integer + */ + quantity: number; + + subscriptionId: string; + + type: 'static'; + + unitPrice: number; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + /** + * Used as a flag to soft delete a subscription item without losing its history for + * auditability. If set, it will be removed from the subscription items list and + * will not be included in the billing period item list. Epoch milliseconds. + */ + expiredAt?: number | null; + + /** + * JSON object + */ + metadata?: { [key: string]: string | number | boolean } | null; + } + + /** + * Experimental fields. May change without notice. + */ + export interface Experimental { + featureItems: Array< + | CustomersAPI.ToggleSubscriptionItemFeatureRecord + | Experimental.UsageCreditGrantSubscriptionItemFeatureRecord + >; + + usageMeterBalances: Array; + } + + export namespace Experimental { + export interface UsageCreditGrantSubscriptionItemFeatureRecord { + id: string; + + amount: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + featureId: string; + + livemode: boolean; + + name: string; + + renewalFrequency: 'once' | 'every_billing_period'; + + slug: string; + + subscriptionItemId: string; + + type: 'usage_credit_grant'; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + usageMeterId: string; + + /** + * Epoch milliseconds. + */ + detachedAt?: number | null; + + detachedReason?: string | null; + + /** + * Epoch milliseconds. + */ + expiredAt?: number | null; + + productFeatureId?: string | null; + } + + /** + * A usage meter and the available balance for that meter, scoped to a given + * subscription. + */ + export interface UsageMeterBalance { + id: string; + + /** + * The type of aggregation to perform on the usage meter. Defaults to "sum", which + * aggregates all the usage event amounts for the billing period. + * "count_distinct_properties" counts the number of distinct properties in the + * billing period for a given meter. + */ + aggregationType: 'sum' | 'count_distinct_properties'; + + availableBalance: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + livemode: boolean; + + name: string; + + organizationId: string; + + pricingModelId: string; + + slug: string; + + subscriptionId: string; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + } + } + } + + export interface StandardSubscriptionDetails { + id: string; + + backupPaymentMethodId: string | null; + + cancellationReason: string | null; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + current: boolean; + + customerId: string; + + defaultPaymentMethodId: string | null; + + interval: 'day' | 'week' | 'month' | 'year'; + + /** + * A positive integer + */ + intervalCount: number; + + isFreePlan: boolean | null; + + livemode: boolean; + + name: string | null; + + organizationId: string; + + priceId: string | null; + + renews: true; + + replacedBySubscriptionId: string | null; + + runBillingAtPeriodStart: boolean | null; + + /** + * Epoch milliseconds. + */ + startDate: number; + + status: + | 'trialing' + | 'active' + | 'past_due' + | 'unpaid' + | 'cancellation_scheduled' + | 'incomplete' + | 'incomplete_expired' + | 'canceled' + | 'paused'; + + subscriptionItems: Array; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + /** + * Epoch milliseconds. + */ + billingCycleAnchorDate?: number | null; + + /** + * Epoch milliseconds. + */ + canceledAt?: number | null; + + /** + * Epoch milliseconds. + */ + cancelScheduledAt?: number | null; + + /** + * Epoch milliseconds. + */ + currentBillingPeriodEnd?: number | null; + + /** + * Epoch milliseconds. + */ + currentBillingPeriodStart?: number | null; + + /** + * Experimental fields. May change without notice. + */ + experimental?: StandardSubscriptionDetails.Experimental; + + /** + * JSON object + */ + metadata?: { [key: string]: string | number | boolean } | null; + + /** + * Epoch milliseconds. + */ + trialEnd?: number | null; + } + + export namespace StandardSubscriptionDetails { + export interface SubscriptionItem { + id: string; + + /** + * Epoch milliseconds. + */ + addedDate: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + externalId: string | null; + + livemode: boolean; + + name: string | null; + + price: + | PricesAPI.SubscriptionPriceClientSelectSchema + | PricesAPI.SinglePaymentPriceClientSelectSchema + | PricesAPI.UsagePriceClientSelectSchema; + + priceId: string; + + /** + * A positive integer + */ + quantity: number; + + subscriptionId: string; + + type: 'static'; + + unitPrice: number; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + /** + * Used as a flag to soft delete a subscription item without losing its history for + * auditability. If set, it will be removed from the subscription items list and + * will not be included in the billing period item list. Epoch milliseconds. + */ + expiredAt?: number | null; + + /** + * JSON object + */ + metadata?: { [key: string]: string | number | boolean } | null; + } + + /** + * Experimental fields. May change without notice. + */ + export interface Experimental { + featureItems: Array< + | CustomersAPI.ToggleSubscriptionItemFeatureRecord + | Experimental.UsageCreditGrantSubscriptionItemFeatureRecord + >; + + usageMeterBalances: Array; + } + + export namespace Experimental { + export interface UsageCreditGrantSubscriptionItemFeatureRecord { + id: string; + + amount: number; + + /** + * Epoch milliseconds. + */ + createdAt: number; + + featureId: string; + + livemode: boolean; + + name: string; + + renewalFrequency: 'once' | 'every_billing_period'; + + slug: string; + + subscriptionItemId: string; + + type: 'usage_credit_grant'; + + /** + * Epoch milliseconds. + */ + updatedAt: number; + + usageMeterId: string; + + /** + * Epoch milliseconds. + */ + detachedAt?: number | null; + + detachedReason?: string | null; + + /** + * Epoch milliseconds. + */ + expiredAt?: number | null; + + productFeatureId?: string | null; + } + /** * A usage meter and the available balance for that meter, scoped to a given * subscription. @@ -1492,7 +2633,7 @@ export declare namespace Customers { export { type CustomerClientSelectSchema as CustomerClientSelectSchema, type ToggleSubscriptionItemFeatureRecord as ToggleSubscriptionItemFeatureRecord, - type UsageCreditGrantSubscriptionItemFeatureClientSelectSchema as UsageCreditGrantSubscriptionItemFeatureClientSelectSchema, + type UsageCreditGrantSubscriptionItemFeatureRecord as UsageCreditGrantSubscriptionItemFeatureRecord, type CustomerCreateResponse as CustomerCreateResponse, type CustomerRetrieveResponse as CustomerRetrieveResponse, type CustomerUpdateResponse as CustomerUpdateResponse, diff --git a/src/resources/index.ts b/src/resources/index.ts index 98b4d9d1..a02950c6 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -18,7 +18,7 @@ export { Customers, type CustomerClientSelectSchema, type ToggleSubscriptionItemFeatureRecord, - type UsageCreditGrantSubscriptionItemFeatureClientSelectSchema, + type UsageCreditGrantSubscriptionItemFeatureRecord, type CustomerCreateResponse, type CustomerRetrieveResponse, type CustomerUpdateResponse, diff --git a/src/resources/pricing-models.ts b/src/resources/pricing-models.ts index 752afb66..f2ca0f8c 100644 --- a/src/resources/pricing-models.ts +++ b/src/resources/pricing-models.ts @@ -69,6 +69,8 @@ export interface PricingModelClientSelectSchema { */ createdAt: number; + integrationGuideHash: string | null; + isDefault: boolean; livemode: boolean; @@ -125,6 +127,8 @@ export namespace PricingModelCreateParams { export interface PricingModel { name: string; + integrationGuideHash?: string | null; + isDefault?: boolean; } } @@ -137,6 +141,8 @@ export namespace PricingModelUpdateParams { export interface PricingModel { id: string; + integrationGuideHash?: string | null; + isDefault?: boolean; name?: string; diff --git a/src/resources/products.ts b/src/resources/products.ts index 70c0c721..0a8bbdd8 100644 --- a/src/resources/products.ts +++ b/src/resources/products.ts @@ -326,9 +326,9 @@ export interface ProductUpdateParams { featureIds?: Array; price?: - | ProductUpdateParams.SubscriptionPriceClientUpdateSchema - | ProductUpdateParams.SinglePaymentPriceClientUpdateSchema - | ProductUpdateParams.UsagePriceClientUpdateSchema; + | ProductUpdateParams.SubscriptionPriceClientInsertSchema + | ProductUpdateParams.SinglePaymentPriceClientInsertSchema + | ProductUpdateParams.UsagePriceClientInsertSchema; } export namespace ProductUpdateParams { @@ -352,55 +352,148 @@ export namespace ProductUpdateParams { slug?: string | null; } - export interface SubscriptionPriceClientUpdateSchema { - id: string; + export interface SubscriptionPriceClientInsertSchema { + /** + * A positive integer + */ + intervalCount: number; + + intervalUnit: 'day' | 'week' | 'month' | 'year'; /** * Whether or not this price is the default price for the product. */ isDefault: boolean; + productId: string; + type: 'subscription'; + /** + * The price per unit. This should be in the smallest unit of the currency. For + * example, if the currency is USD, GBP, CAD, EUR or SGD, the price should be in + * cents. + */ + unitPrice: number; + active?: boolean; name?: string | null; slug?: string | null; - } - export interface SinglePaymentPriceClientUpdateSchema { - id: string; + /** + * The trial period in days. If the trial period is 0 or null, there will be no + * trial period. + */ + trialPeriodDays?: number | null; + /** + * Omitted. + */ + usageEventsPerUnit?: null; + + /** + * Omitted. + */ + usageMeterId?: null; + } + + export interface SinglePaymentPriceClientInsertSchema { /** * Whether or not this price is the default price for the product. */ isDefault: boolean; + productId: string; + type: 'single_payment'; + /** + * The price per unit. This should be in the smallest unit of the currency. For + * example, if the currency is USD, GBP, CAD, EUR or SGD, the price should be in + * cents. + */ + unitPrice: number; + active?: boolean; + /** + * Omitted. + */ + intervalCount?: null; + + /** + * Omitted. + */ + intervalUnit?: null; + name?: string | null; slug?: string | null; + + /** + * Omitted. + */ + trialPeriodDays?: null; + + /** + * Omitted. + */ + usageEventsPerUnit?: null; + + /** + * Omitted. + */ + usageMeterId?: null; } - export interface UsagePriceClientUpdateSchema { - id: string; + export interface UsagePriceClientInsertSchema { + /** + * A positive integer + */ + intervalCount: number; + + intervalUnit: 'day' | 'week' | 'month' | 'year'; /** * Whether or not this price is the default price for the product. */ isDefault: boolean; + productId: string; + type: 'usage'; + /** + * The price per unit. This should be in the smallest unit of the currency. For + * example, if the currency is USD, GBP, CAD, EUR or SGD, the price should be in + * cents. + */ + unitPrice: number; + + /** + * The number of usage events per unit. Used to determine how to map usage events + * to quantities when raising invoices for usage. + */ + usageEventsPerUnit: number; + + /** + * The usage meter that uses this price. All usage events on that meter must be + * associated with a price that is also associated with that usage meter. + */ + usageMeterId: string; + active?: boolean; name?: string | null; slug?: string | null; + + /** + * Omitted. + */ + trialPeriodDays?: null; } } diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 4c4c0e26..f00ff5ff 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -218,6 +218,8 @@ export interface PricingModelDetailsRecord { */ createdAt: number; + integrationGuideHash: string | null; + isDefault: boolean; livemode: boolean; diff --git a/src/resources/subscriptions.ts b/src/resources/subscriptions.ts index fe50aa25..589ba005 100644 --- a/src/resources/subscriptions.ts +++ b/src/resources/subscriptions.ts @@ -45,7 +45,7 @@ export class Subscriptions extends APIResource { } /** - * Cancel a Subscription + * Cancel Subscription */ cancel( id: string, @@ -140,22 +140,21 @@ export interface SubscriptionCancelResponse { export interface SubscriptionCreateParams { /** - * The customer for the subscription. + * The payment method to try if charges for the subscription fail with the default + * payment method. */ - customerId: string; + backupPaymentMethodId?: string; /** - * The price to subscribe to. Used to determine whether the subscription is - * usage-based or not, and set other defaults such as trial period and billing - * intervals. + * The external ID of the customer. If not provided, customerId is required. */ - priceId: string; + customerExternalId?: string; /** - * The payment method to try if charges for the subscription fail with the default - * payment method. + * The internal ID of the customer. If not provided, customerExternalId is + * required. */ - backupPaymentMethodId?: string; + customerId?: string; /** * The default payment method to use when attempting to run charges for the @@ -169,7 +168,7 @@ export interface SubscriptionCreateParams { /** * The interval of the subscription. If not provided, defaults to the interval of - * the price provided by `priceId`. + * the price provided by `priceId` or `priceSlug`. */ interval?: 'day' | 'week' | 'month' | 'year'; @@ -186,10 +185,25 @@ export interface SubscriptionCreateParams { /** * The name of the subscription. If not provided, defaults to the name of the - * product associated with the price provided by 'priceId'. + * product associated with the price provided by 'priceId' or 'priceSlug'. */ name?: string; + /** + * The id of the price to subscribe to. If not provided, priceSlug is required. + * Used to determine whether the subscription is usage-based or not, and set other + * defaults such as trial period and billing intervals. + */ + priceId?: string; + + /** + * The slug of the price to subscribe to. If not provided, priceId is required. + * Price slugs are scoped to the customer's pricing model. Used to determine + * whether the subscription is usage-based or not, and set other defaults such as + * trial period and billing intervals. + */ + priceSlug?: string; + /** * The quantity of the price purchased. If not provided, defaults to 1. */ diff --git a/src/resources/usage-events.ts b/src/resources/usage-events.ts index 301f2ea1..5ffabfec 100644 --- a/src/resources/usage-events.ts +++ b/src/resources/usage-events.ts @@ -75,7 +75,7 @@ export namespace UsageEventCreateResponse { * Properties for the usage event. Only required when using the * "count_distinct_properties" aggregation type. */ - properties?: { [key: string]: unknown }; + properties?: { [key: string]: unknown } | null; } } @@ -133,7 +133,7 @@ export namespace UsageEventRetrieveResponse { * Properties for the usage event. Only required when using the * "count_distinct_properties" aggregation type. */ - properties?: { [key: string]: unknown }; + properties?: { [key: string]: unknown } | null; } } @@ -145,8 +145,6 @@ export namespace UsageEventCreateParams { export interface UsageEvent { amount: number; - priceId: string; - subscriptionId: string; /** @@ -155,11 +153,21 @@ export namespace UsageEventCreateParams { */ transactionId: string; + /** + * The internal ID of the price. If not provided, priceSlug is required. + */ + priceId?: string; + + /** + * The slug of the price. If not provided, priceId is required. + */ + priceSlug?: string; + /** * Properties for the usage event. Only required when using the * "count_distinct_properties" aggregation type. */ - properties?: { [key: string]: unknown }; + properties?: { [key: string]: unknown } | null; /** * The date the usage occurred. If the usage occurs in a date that is outside of diff --git a/src/resources/usage-meters.ts b/src/resources/usage-meters.ts index 8671e479..2e029599 100644 --- a/src/resources/usage-meters.ts +++ b/src/resources/usage-meters.ts @@ -69,6 +69,8 @@ export interface UsageMeterListResponse { export interface UsageMeterCreateParams { usageMeter: UsageMeterCreateParams.UsageMeter; + + price?: UsageMeterCreateParams.Price; } export namespace UsageMeterCreateParams { @@ -87,6 +89,14 @@ export namespace UsageMeterCreateParams { */ aggregationType?: 'sum' | 'count_distinct_properties'; } + + export interface Price { + type?: 'single_payment' | 'subscription' | 'usage'; + + unitPrice?: number; + + usageEventsPerUnit?: number; + } } export interface UsageMeterUpdateParams { diff --git a/src/version.ts b/src/version.ts index db66d332..d77fad47 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.22.0'; // x-release-please-version +export const VERSION = '0.23.0'; // x-release-please-version diff --git a/tests/api-resources/checkout-sessions.test.ts b/tests/api-resources/checkout-sessions.test.ts index fcb3cb42..88ede01a 100644 --- a/tests/api-resources/checkout-sessions.test.ts +++ b/tests/api-resources/checkout-sessions.test.ts @@ -14,7 +14,6 @@ describe('resource checkoutSessions', () => { checkoutSession: { cancelUrl: 'cancelUrl', customerExternalId: 'customerExternalId', - priceId: 'priceId', successUrl: 'successUrl', type: 'product', }, @@ -40,7 +39,7 @@ describe('resource checkoutSessions', () => { outputMetadata: { foo: 'string' }, outputName: 'outputName', preserveBillingCycleAnchor: true, - priceId: '', + priceId: 'abc', quantity: 0, }, }); diff --git a/tests/api-resources/pricing-models.test.ts b/tests/api-resources/pricing-models.test.ts index 02bf1e53..63bd53a1 100644 --- a/tests/api-resources/pricing-models.test.ts +++ b/tests/api-resources/pricing-models.test.ts @@ -23,7 +23,7 @@ describe('resource pricingModels', () => { // Prism tests are disabled test.skip('create: required and optional params', async () => { const response = await client.pricingModels.create({ - pricingModel: { name: 'name', isDefault: true }, + pricingModel: { name: 'name', integrationGuideHash: 'integrationGuideHash', isDefault: true }, defaultPlanIntervalUnit: 'day', }); }); @@ -55,7 +55,7 @@ describe('resource pricingModels', () => { // Prism tests are disabled test.skip('update: required and optional params', async () => { const response = await client.pricingModels.update('id', { - pricingModel: { id: 'id', isDefault: true, name: 'name' }, + pricingModel: { id: 'id', integrationGuideHash: 'integrationGuideHash', isDefault: true, name: 'name' }, }); }); diff --git a/tests/api-resources/products.test.ts b/tests/api-resources/products.test.ts index ec63d516..f7553983 100644 --- a/tests/api-resources/products.test.ts +++ b/tests/api-resources/products.test.ts @@ -95,7 +95,20 @@ describe('resource products', () => { slug: 'slug', }, featureIds: ['string'], - price: { id: 'id', isDefault: true, type: 'subscription', active: true, name: 'name', slug: 'slug' }, + price: { + intervalCount: 1, + intervalUnit: 'day', + isDefault: true, + productId: 'productId', + type: 'subscription', + unitPrice: 0, + active: true, + name: 'name', + slug: 'slug', + trialPeriodDays: 0, + usageEventsPerUnit: null, + usageMeterId: null, + }, }); }); diff --git a/tests/api-resources/subscriptions.test.ts b/tests/api-resources/subscriptions.test.ts index 3941d9ca..0e8845c4 100644 --- a/tests/api-resources/subscriptions.test.ts +++ b/tests/api-resources/subscriptions.test.ts @@ -9,8 +9,8 @@ const client = new Flowglad({ describe('resource subscriptions', () => { // Prism tests are disabled - test.skip('create: only required params', async () => { - const responsePromise = client.subscriptions.create({ customerId: 'customerId', priceId: 'priceId' }); + test.skip('create', async () => { + const responsePromise = client.subscriptions.create({}); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -20,23 +20,6 @@ describe('resource subscriptions', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled - test.skip('create: required and optional params', async () => { - const response = await client.subscriptions.create({ - customerId: 'customerId', - priceId: 'priceId', - backupPaymentMethodId: 'backupPaymentMethodId', - defaultPaymentMethodId: 'defaultPaymentMethodId', - interval: 'day', - intervalCount: 0, - metadata: { foo: 'string' }, - name: 'name', - quantity: 0, - startDate: 'startDate', - trialEnd: 0, - }); - }); - // Prism tests are disabled test.skip('retrieve', async () => { const responsePromise = client.subscriptions.retrieve('id'); diff --git a/tests/api-resources/usage-events.test.ts b/tests/api-resources/usage-events.test.ts index c469bf89..1fe4591f 100644 --- a/tests/api-resources/usage-events.test.ts +++ b/tests/api-resources/usage-events.test.ts @@ -11,12 +11,7 @@ describe('resource usageEvents', () => { // Prism tests are disabled test.skip('create: only required params', async () => { const responsePromise = client.usageEvents.create({ - usageEvent: { - amount: 1, - priceId: 'priceId', - subscriptionId: 'subscriptionId', - transactionId: 'transactionId', - }, + usageEvent: { amount: 1, subscriptionId: 'subscriptionId', transactionId: 'transactionId' }, }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); @@ -32,9 +27,10 @@ describe('resource usageEvents', () => { const response = await client.usageEvents.create({ usageEvent: { amount: 1, - priceId: 'priceId', subscriptionId: 'subscriptionId', transactionId: 'transactionId', + priceId: 'priceId', + priceSlug: 'priceSlug', properties: { foo: 'bar' }, usageDate: -9007199254740991, }, diff --git a/tests/api-resources/usage-meters.test.ts b/tests/api-resources/usage-meters.test.ts index b53c9d5a..5f76450d 100644 --- a/tests/api-resources/usage-meters.test.ts +++ b/tests/api-resources/usage-meters.test.ts @@ -26,6 +26,7 @@ describe('resource usageMeters', () => { test.skip('create: required and optional params', async () => { const response = await client.usageMeters.create({ usageMeter: { name: 'name', pricingModelId: 'pricingModelId', slug: 'slug', aggregationType: 'sum' }, + price: { type: 'single_payment', unitPrice: 0, usageEventsPerUnit: 0 }, }); });