#
tokens: 48861/50000 30/260 files (page 3/8)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 8. Use http://codebase.md/dodopayments/dodopayments-node?page={x} to view the full context.

# Directory Structure

```
├── .devcontainer
│   └── devcontainer.json
├── .github
│   └── workflows
│       ├── ci.yml
│       ├── docker-mcp.yml
│       ├── publish-npm.yml
│       └── release-doctor.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .release-please-manifest.json
├── .stats.yml
├── api.md
├── bin
│   ├── check-release-environment
│   ├── cli
│   ├── docker-tags
│   ├── migration-config.json
│   └── publish-npm
├── Brewfile
├── CHANGELOG.md
├── CONTRIBUTING.md
├── eslint.config.mjs
├── examples
│   └── .keep
├── jest.config.ts
├── LICENSE
├── MIGRATION.md
├── package.json
├── packages
│   └── mcp-server
│       ├── .dockerignore
│       ├── build
│       ├── cloudflare-worker
│       │   ├── .gitignore
│       │   ├── biome.json
│       │   ├── package.json
│       │   ├── README.md
│       │   ├── src
│       │   │   ├── app.ts
│       │   │   ├── index.ts
│       │   │   └── utils.ts
│       │   ├── static
│       │   │   └── home.md
│       │   ├── tsconfig.json
│       │   ├── worker-configuration.d.ts
│       │   └── wrangler.jsonc
│       ├── Dockerfile
│       ├── jest.config.ts
│       ├── manifest.json
│       ├── package.json
│       ├── README.md
│       ├── scripts
│       │   ├── copy-bundle-files.cjs
│       │   └── postprocess-dist-package-json.cjs
│       ├── src
│       │   ├── code-tool-paths.cts
│       │   ├── code-tool-types.ts
│       │   ├── code-tool-worker.ts
│       │   ├── code-tool.ts
│       │   ├── compat.ts
│       │   ├── docs-search-tool.ts
│       │   ├── dynamic-tools.ts
│       │   ├── filtering.ts
│       │   ├── headers.ts
│       │   ├── http.ts
│       │   ├── index.ts
│       │   ├── options.ts
│       │   ├── server.ts
│       │   ├── stdio.ts
│       │   ├── tools
│       │   │   ├── addons
│       │   │   │   ├── create-addons.ts
│       │   │   │   ├── list-addons.ts
│       │   │   │   ├── retrieve-addons.ts
│       │   │   │   ├── update-addons.ts
│       │   │   │   └── update-images-addons.ts
│       │   │   ├── brands
│       │   │   │   ├── create-brands.ts
│       │   │   │   ├── list-brands.ts
│       │   │   │   ├── retrieve-brands.ts
│       │   │   │   ├── update-brands.ts
│       │   │   │   └── update-images-brands.ts
│       │   │   ├── checkout-sessions
│       │   │   │   ├── create-checkout-sessions.ts
│       │   │   │   └── retrieve-checkout-sessions.ts
│       │   │   ├── customers
│       │   │   │   ├── create-customers.ts
│       │   │   │   ├── customer-portal
│       │   │   │   │   └── create-customers-customer-portal.ts
│       │   │   │   ├── list-customers.ts
│       │   │   │   ├── retrieve-customers.ts
│       │   │   │   ├── update-customers.ts
│       │   │   │   └── wallets
│       │   │   │       ├── ledger-entries
│       │   │   │       │   ├── create-wallets-customers-ledger-entries.ts
│       │   │   │       │   └── list-wallets-customers-ledger-entries.ts
│       │   │   │       └── list-customers-wallets.ts
│       │   │   ├── discounts
│       │   │   │   ├── create-discounts.ts
│       │   │   │   ├── delete-discounts.ts
│       │   │   │   ├── list-discounts.ts
│       │   │   │   ├── retrieve-discounts.ts
│       │   │   │   └── update-discounts.ts
│       │   │   ├── disputes
│       │   │   │   ├── list-disputes.ts
│       │   │   │   └── retrieve-disputes.ts
│       │   │   ├── index.ts
│       │   │   ├── invoices
│       │   │   │   └── payments
│       │   │   │       ├── retrieve-invoices-payments.ts
│       │   │   │       └── retrieve-refund-invoices-payments.ts
│       │   │   ├── license-key-instances
│       │   │   │   ├── list-license-key-instances.ts
│       │   │   │   ├── retrieve-license-key-instances.ts
│       │   │   │   └── update-license-key-instances.ts
│       │   │   ├── license-keys
│       │   │   │   ├── list-license-keys.ts
│       │   │   │   ├── retrieve-license-keys.ts
│       │   │   │   └── update-license-keys.ts
│       │   │   ├── licenses
│       │   │   │   ├── activate-licenses.ts
│       │   │   │   ├── deactivate-licenses.ts
│       │   │   │   └── validate-licenses.ts
│       │   │   ├── meters
│       │   │   │   ├── archive-meters.ts
│       │   │   │   ├── create-meters.ts
│       │   │   │   ├── list-meters.ts
│       │   │   │   ├── retrieve-meters.ts
│       │   │   │   └── unarchive-meters.ts
│       │   │   ├── misc
│       │   │   │   └── list-supported-countries-misc.ts
│       │   │   ├── payments
│       │   │   │   ├── create-payments.ts
│       │   │   │   ├── list-payments.ts
│       │   │   │   ├── retrieve-line-items-payments.ts
│       │   │   │   └── retrieve-payments.ts
│       │   │   ├── payouts
│       │   │   │   └── list-payouts.ts
│       │   │   ├── products
│       │   │   │   ├── archive-products.ts
│       │   │   │   ├── create-products.ts
│       │   │   │   ├── images
│       │   │   │   │   └── update-products-images.ts
│       │   │   │   ├── list-products.ts
│       │   │   │   ├── retrieve-products.ts
│       │   │   │   ├── unarchive-products.ts
│       │   │   │   ├── update-files-products.ts
│       │   │   │   └── update-products.ts
│       │   │   ├── refunds
│       │   │   │   ├── create-refunds.ts
│       │   │   │   ├── list-refunds.ts
│       │   │   │   └── retrieve-refunds.ts
│       │   │   ├── subscriptions
│       │   │   │   ├── change-plan-subscriptions.ts
│       │   │   │   ├── charge-subscriptions.ts
│       │   │   │   ├── create-subscriptions.ts
│       │   │   │   ├── list-subscriptions.ts
│       │   │   │   ├── retrieve-subscriptions.ts
│       │   │   │   ├── retrieve-usage-history-subscriptions.ts
│       │   │   │   └── update-subscriptions.ts
│       │   │   ├── types.ts
│       │   │   ├── usage-events
│       │   │   │   ├── ingest-usage-events.ts
│       │   │   │   ├── list-usage-events.ts
│       │   │   │   └── retrieve-usage-events.ts
│       │   │   └── webhooks
│       │   │       ├── create-webhooks.ts
│       │   │       ├── delete-webhooks.ts
│       │   │       ├── headers
│       │   │       │   ├── retrieve-webhooks-headers.ts
│       │   │       │   └── update-webhooks-headers.ts
│       │   │       ├── list-webhooks.ts
│       │   │       ├── retrieve-secret-webhooks.ts
│       │   │       ├── retrieve-webhooks.ts
│       │   │       └── update-webhooks.ts
│       │   └── tools.ts
│       ├── tests
│       │   ├── compat.test.ts
│       │   ├── dynamic-tools.test.ts
│       │   ├── options.test.ts
│       │   └── tools.test.ts
│       ├── tsc-multi.json
│       ├── tsconfig.build.json
│       ├── tsconfig.dist-src.json
│       ├── tsconfig.json
│       └── yarn.lock
├── README.md
├── release-please-config.json
├── scripts
│   ├── bootstrap
│   ├── build
│   ├── build-all
│   ├── fast-format
│   ├── format
│   ├── lint
│   ├── mock
│   ├── publish-packages.ts
│   ├── test
│   └── utils
│       ├── attw-report.cjs
│       ├── check-is-in-git-install.sh
│       ├── check-version.cjs
│       ├── fix-index-exports.cjs
│       ├── git-swap.sh
│       ├── make-dist-package-json.cjs
│       ├── postprocess-files.cjs
│       └── upload-artifact.sh
├── SECURITY.md
├── src
│   ├── api-promise.ts
│   ├── client.ts
│   ├── core
│   │   ├── api-promise.ts
│   │   ├── error.ts
│   │   ├── pagination.ts
│   │   ├── README.md
│   │   ├── resource.ts
│   │   └── uploads.ts
│   ├── error.ts
│   ├── index.ts
│   ├── internal
│   │   ├── builtin-types.ts
│   │   ├── detect-platform.ts
│   │   ├── errors.ts
│   │   ├── headers.ts
│   │   ├── parse.ts
│   │   ├── README.md
│   │   ├── request-options.ts
│   │   ├── shim-types.ts
│   │   ├── shims.ts
│   │   ├── to-file.ts
│   │   ├── types.ts
│   │   ├── uploads.ts
│   │   ├── utils
│   │   │   ├── base64.ts
│   │   │   ├── bytes.ts
│   │   │   ├── env.ts
│   │   │   ├── log.ts
│   │   │   ├── path.ts
│   │   │   ├── sleep.ts
│   │   │   ├── uuid.ts
│   │   │   └── values.ts
│   │   └── utils.ts
│   ├── lib
│   │   └── .keep
│   ├── pagination.ts
│   ├── resource.ts
│   ├── resources
│   │   ├── addons.ts
│   │   ├── brands.ts
│   │   ├── checkout-sessions.ts
│   │   ├── customers
│   │   │   ├── customer-portal.ts
│   │   │   ├── customers.ts
│   │   │   ├── index.ts
│   │   │   ├── wallets
│   │   │   │   ├── index.ts
│   │   │   │   ├── ledger-entries.ts
│   │   │   │   └── wallets.ts
│   │   │   └── wallets.ts
│   │   ├── customers.ts
│   │   ├── discounts.ts
│   │   ├── disputes.ts
│   │   ├── index.ts
│   │   ├── invoices
│   │   │   ├── index.ts
│   │   │   ├── invoices.ts
│   │   │   └── payments.ts
│   │   ├── invoices.ts
│   │   ├── license-key-instances.ts
│   │   ├── license-keys.ts
│   │   ├── licenses.ts
│   │   ├── meters.ts
│   │   ├── misc.ts
│   │   ├── payments.ts
│   │   ├── payouts.ts
│   │   ├── products
│   │   │   ├── images.ts
│   │   │   ├── index.ts
│   │   │   └── products.ts
│   │   ├── products.ts
│   │   ├── refunds.ts
│   │   ├── subscriptions.ts
│   │   ├── usage-events.ts
│   │   ├── webhook-events.ts
│   │   ├── webhooks
│   │   │   ├── headers.ts
│   │   │   ├── index.ts
│   │   │   └── webhooks.ts
│   │   └── webhooks.ts
│   ├── resources.ts
│   ├── uploads.ts
│   └── version.ts
├── tests
│   ├── api-resources
│   │   ├── addons.test.ts
│   │   ├── brands.test.ts
│   │   ├── checkout-sessions.test.ts
│   │   ├── customers
│   │   │   ├── customer-portal.test.ts
│   │   │   ├── customers.test.ts
│   │   │   └── wallets
│   │   │       ├── ledger-entries.test.ts
│   │   │       └── wallets.test.ts
│   │   ├── discounts.test.ts
│   │   ├── disputes.test.ts
│   │   ├── license-key-instances.test.ts
│   │   ├── license-keys.test.ts
│   │   ├── licenses.test.ts
│   │   ├── meters.test.ts
│   │   ├── misc.test.ts
│   │   ├── payments.test.ts
│   │   ├── payouts.test.ts
│   │   ├── products
│   │   │   ├── images.test.ts
│   │   │   └── products.test.ts
│   │   ├── refunds.test.ts
│   │   ├── subscriptions.test.ts
│   │   ├── usage-events.test.ts
│   │   └── webhooks
│   │       ├── headers.test.ts
│   │       └── webhooks.test.ts
│   ├── base64.test.ts
│   ├── buildHeaders.test.ts
│   ├── form.test.ts
│   ├── index.test.ts
│   ├── path.test.ts
│   ├── stringifyQuery.test.ts
│   └── uploads.test.ts
├── tsc-multi.json
├── tsconfig.build.json
├── tsconfig.deno.json
├── tsconfig.dist-src.json
├── tsconfig.json
└── yarn.lock
```

# Files

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/license-keys/update-license-keys.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'license_keys',
  operation: 'write',
  tags: [],
  httpMethod: 'patch',
  httpPath: '/license_keys/{id}',
  operationId: 'update_license_key',
};

export const tool: Tool = {
  name: 'update_license_keys',
  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\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/license_key',\n  $defs: {\n    license_key: {\n      type: 'object',\n      properties: {\n        id: {\n          type: 'string',\n          description: 'The unique identifier of the license key.'\n        },\n        business_id: {\n          type: 'string',\n          description: 'The unique identifier of the business associated with the license key.'\n        },\n        created_at: {\n          type: 'string',\n          description: 'The timestamp indicating when the license key was created, in UTC.',\n          format: 'date-time'\n        },\n        customer_id: {\n          type: 'string',\n          description: 'The unique identifier of the customer associated with the license key.'\n        },\n        instances_count: {\n          type: 'integer',\n          description: 'The current number of instances activated for this license key.'\n        },\n        key: {\n          type: 'string',\n          description: 'The license key string.'\n        },\n        payment_id: {\n          type: 'string',\n          description: 'The unique identifier of the payment associated with the license key.'\n        },\n        product_id: {\n          type: 'string',\n          description: 'The unique identifier of the product associated with the license key.'\n        },\n        status: {\n          $ref: '#/$defs/license_key_status'\n        },\n        activations_limit: {\n          type: 'integer',\n          description: 'The maximum number of activations allowed for this license key.'\n        },\n        expires_at: {\n          type: 'string',\n          description: 'The timestamp indicating when the license key expires, in UTC.',\n          format: 'date-time'\n        },\n        subscription_id: {\n          type: 'string',\n          description: 'The unique identifier of the subscription associated with the license key, if any.'\n        }\n      },\n      required: [        'id',\n        'business_id',\n        'created_at',\n        'customer_id',\n        'instances_count',\n        'key',\n        'payment_id',\n        'product_id',\n        'status'\n      ]\n    },\n    license_key_status: {\n      type: 'string',\n      enum: [        'active',\n        'expired',\n        'disabled'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
      activations_limit: {
        type: 'integer',
        description:
          'The updated activation limit for the license key.\nUse `null` to remove the limit, or omit this field to leave it unchanged.',
      },
      disabled: {
        type: 'boolean',
        description:
          'Indicates whether the license key should be disabled.\nA value of `true` disables the key, while `false` enables it. Omit this field to leave it unchanged.',
      },
      expires_at: {
        type: 'string',
        description:
          'The updated expiration timestamp for the license key in UTC.\nUse `null` to remove the expiration date, or omit this field to leave it unchanged.',
        format: 'date-time',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['id'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.licenseKeys.update(id, body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/license-keys/list-license-keys.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'license_keys',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/license_keys',
  operationId: 'list_license_keys_handler',
};

export const tool: Tool = {
  name: 'list_license_keys',
  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\n\n\n# Response Schema\n```json\n{\n  type: 'object',\n  properties: {\n    items: {\n      type: 'array',\n      items: {\n        $ref: '#/$defs/license_key'\n      }\n    }\n  },\n  required: [    'items'\n  ],\n  $defs: {\n    license_key: {\n      type: 'object',\n      properties: {\n        id: {\n          type: 'string',\n          description: 'The unique identifier of the license key.'\n        },\n        business_id: {\n          type: 'string',\n          description: 'The unique identifier of the business associated with the license key.'\n        },\n        created_at: {\n          type: 'string',\n          description: 'The timestamp indicating when the license key was created, in UTC.',\n          format: 'date-time'\n        },\n        customer_id: {\n          type: 'string',\n          description: 'The unique identifier of the customer associated with the license key.'\n        },\n        instances_count: {\n          type: 'integer',\n          description: 'The current number of instances activated for this license key.'\n        },\n        key: {\n          type: 'string',\n          description: 'The license key string.'\n        },\n        payment_id: {\n          type: 'string',\n          description: 'The unique identifier of the payment associated with the license key.'\n        },\n        product_id: {\n          type: 'string',\n          description: 'The unique identifier of the product associated with the license key.'\n        },\n        status: {\n          $ref: '#/$defs/license_key_status'\n        },\n        activations_limit: {\n          type: 'integer',\n          description: 'The maximum number of activations allowed for this license key.'\n        },\n        expires_at: {\n          type: 'string',\n          description: 'The timestamp indicating when the license key expires, in UTC.',\n          format: 'date-time'\n        },\n        subscription_id: {\n          type: 'string',\n          description: 'The unique identifier of the subscription associated with the license key, if any.'\n        }\n      },\n      required: [        'id',\n        'business_id',\n        'created_at',\n        'customer_id',\n        'instances_count',\n        'key',\n        'payment_id',\n        'product_id',\n        'status'\n      ]\n    },\n    license_key_status: {\n      type: 'string',\n      enum: [        'active',\n        'expired',\n        'disabled'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      customer_id: {
        type: 'string',
        description: 'Filter by customer ID',
      },
      page_number: {
        type: 'integer',
        description: 'Page number default is 0',
      },
      page_size: {
        type: 'integer',
        description: 'Page size default is 10 max is 100',
      },
      product_id: {
        type: 'string',
        description: 'Filter by product ID',
      },
      status: {
        type: 'string',
        description: 'Filter by license key status',
        enum: ['active', 'expired', 'disabled'],
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: [],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter, ...body } = args as any;
  const response = await client.licenseKeys.list(body).asResponse();
  return asTextContentResult(await maybeFilter(jq_filter, await response.json()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/disputes/list-disputes.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'disputes',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/disputes',
  operationId: 'list_disputes',
};

export const tool: Tool = {
  name: 'list_disputes',
  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\n\n\n# Response Schema\n```json\n{\n  type: 'object',\n  properties: {\n    items: {\n      type: 'array',\n      items: {\n        $ref: '#/$defs/dispute_list_response'\n      }\n    }\n  },\n  required: [    'items'\n  ],\n  $defs: {\n    dispute_list_response: {\n      type: 'object',\n      properties: {\n        amount: {\n          type: 'string',\n          description: 'The amount involved in the dispute, represented as a string to accommodate precision.'\n        },\n        business_id: {\n          type: 'string',\n          description: 'The unique identifier of the business involved in the dispute.'\n        },\n        created_at: {\n          type: 'string',\n          description: 'The timestamp of when the dispute was created, in UTC.',\n          format: 'date-time'\n        },\n        currency: {\n          type: 'string',\n          description: 'The currency of the disputed amount, represented as an ISO 4217 currency code.'\n        },\n        dispute_id: {\n          type: 'string',\n          description: 'The unique identifier of the dispute.'\n        },\n        dispute_stage: {\n          $ref: '#/$defs/dispute_stage'\n        },\n        dispute_status: {\n          $ref: '#/$defs/dispute_status'\n        },\n        payment_id: {\n          type: 'string',\n          description: 'The unique identifier of the payment associated with the dispute.'\n        }\n      },\n      required: [        'amount',\n        'business_id',\n        'created_at',\n        'currency',\n        'dispute_id',\n        'dispute_stage',\n        'dispute_status',\n        'payment_id'\n      ]\n    },\n    dispute_stage: {\n      type: 'string',\n      enum: [        'pre_dispute',\n        'dispute',\n        'pre_arbitration'\n      ]\n    },\n    dispute_status: {\n      type: 'string',\n      enum: [        'dispute_opened',\n        'dispute_expired',\n        'dispute_accepted',\n        'dispute_cancelled',\n        'dispute_challenged',\n        'dispute_won',\n        'dispute_lost'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      created_at_gte: {
        type: 'string',
        description: 'Get events after this created time',
        format: 'date-time',
      },
      created_at_lte: {
        type: 'string',
        description: 'Get events created before this time',
        format: 'date-time',
      },
      customer_id: {
        type: 'string',
        description: 'Filter by customer_id',
      },
      dispute_stage: {
        type: 'string',
        description: 'Filter by dispute stage',
        enum: ['pre_dispute', 'dispute', 'pre_arbitration'],
      },
      dispute_status: {
        type: 'string',
        description: 'Filter by dispute status',
        enum: [
          'dispute_opened',
          'dispute_expired',
          'dispute_accepted',
          'dispute_cancelled',
          'dispute_challenged',
          'dispute_won',
          'dispute_lost',
        ],
      },
      page_number: {
        type: 'integer',
        description: 'Page number default is 0',
      },
      page_size: {
        type: 'integer',
        description: 'Page size default is 10 max is 100',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: [],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter, ...body } = args as any;
  const response = await client.disputes.list(body).asResponse();
  return asTextContentResult(await maybeFilter(jq_filter, await response.json()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/webhooks/update-webhooks.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'webhooks',
  operation: 'write',
  tags: [],
  httpMethod: 'patch',
  httpPath: '/webhooks/{webhook_id}',
  operationId: 'patch_webhook',
};

export const tool: Tool = {
  name: 'update_webhooks',
  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\nPatch a webhook by id\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/webhook_details',\n  $defs: {\n    webhook_details: {\n      type: 'object',\n      title: 'Webhook Details',\n      properties: {\n        id: {\n          type: 'string',\n          description: 'The webhook\\'s ID.'\n        },\n        created_at: {\n          type: 'string',\n          description: 'Created at timestamp'\n        },\n        description: {\n          type: 'string',\n          description: 'An example webhook name.'\n        },\n        metadata: {\n          type: 'object',\n          description: 'Metadata of the webhook',\n          additionalProperties: true\n        },\n        updated_at: {\n          type: 'string',\n          description: 'Updated at timestamp'\n        },\n        url: {\n          type: 'string',\n          description: 'Url endpoint of the webhook'\n        },\n        disabled: {\n          type: 'boolean',\n          description: 'Status of the webhook.\\n\\nIf true, events are not sent'\n        },\n        filter_types: {\n          type: 'array',\n          description: 'Filter events to the webhook.\\n\\nWebhook event will only be sent for events in the list.',\n          items: {\n            type: 'string'\n          }\n        },\n        rate_limit: {\n          type: 'integer',\n          description: 'Configured rate limit'\n        }\n      },\n      required: [        'id',\n        'created_at',\n        'description',\n        'metadata',\n        'updated_at',\n        'url'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      webhook_id: {
        type: 'string',
      },
      description: {
        type: 'string',
        description: 'Description of the webhook',
      },
      disabled: {
        type: 'boolean',
        description: 'To Disable the endpoint, set it to true.',
      },
      filter_types: {
        type: 'array',
        description:
          'Filter events to the endpoint.\n\nWebhook event will only be sent for events in the list.',
        items: {
          $ref: '#/$defs/webhook_event_type',
        },
      },
      metadata: {
        type: 'object',
        description: 'Metadata',
        additionalProperties: true,
      },
      rate_limit: {
        type: 'integer',
        description: 'Rate limit',
      },
      url: {
        type: 'string',
        description: 'Url endpoint',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['webhook_id'],
    $defs: {
      webhook_event_type: {
        type: 'string',
        description: 'Event types for Dodo events',
        enum: [
          'payment.succeeded',
          'payment.failed',
          'payment.processing',
          'payment.cancelled',
          'refund.succeeded',
          'refund.failed',
          'dispute.opened',
          'dispute.expired',
          'dispute.accepted',
          'dispute.cancelled',
          'dispute.challenged',
          'dispute.won',
          'dispute.lost',
          'subscription.active',
          'subscription.renewed',
          'subscription.on_hold',
          'subscription.cancelled',
          'subscription.failed',
          'subscription.expired',
          'subscription.plan_changed',
          'license_key.created',
        ],
      },
    },
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { webhook_id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.webhooks.update(webhook_id, body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/usage-events/list-usage-events.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'usage_events',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/events',
  operationId: 'get_events',
};

export const tool: Tool = {
  name: 'list_usage_events',
  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\nFetch events from your account with powerful filtering capabilities. This endpoint is ideal for:\n- Debugging event ingestion issues\n- Analyzing customer usage patterns\n- Building custom analytics dashboards\n- Auditing billing-related events\n\n## Filtering Options:\n- **Customer filtering**: Filter by specific customer ID\n- **Event name filtering**: Filter by event type/name\n- **Meter-based filtering**: Use a meter ID to apply the meter's event name and filter criteria automatically\n- **Time range filtering**: Filter events within a specific date range\n- **Pagination**: Navigate through large result sets\n\n## Meter Integration:\nWhen using `meter_id`, the endpoint automatically applies:\n- The meter's configured `event_name` filter\n- The meter's custom filter criteria (if any)\n- If you also provide `event_name`, it must match the meter's event name\n\n## Example Queries:\n- Get all events for a customer: `?customer_id=cus_abc123`\n- Get API request events: `?event_name=api_request`\n- Get events from last 24 hours: `?start=2024-01-14T10:30:00Z&end=2024-01-15T10:30:00Z`\n- Get events with meter filtering: `?meter_id=mtr_xyz789`\n- Paginate results: `?page_size=50&page_number=2`\n\n# Response Schema\n```json\n{\n  type: 'object',\n  properties: {\n    items: {\n      type: 'array',\n      items: {\n        $ref: '#/$defs/event'\n      }\n    }\n  },\n  required: [    'items'\n  ],\n  $defs: {\n    event: {\n      type: 'object',\n      properties: {\n        business_id: {\n          type: 'string'\n        },\n        customer_id: {\n          type: 'string'\n        },\n        event_id: {\n          type: 'string'\n        },\n        event_name: {\n          type: 'string'\n        },\n        timestamp: {\n          type: 'string',\n          format: 'date-time'\n        },\n        metadata: {\n          type: 'object',\n          title: 'EventMetadata',\n          description: 'Arbitrary key-value metadata. Values can be string, integer, number, or boolean.',\n          additionalProperties: true\n        }\n      },\n      required: [        'business_id',\n        'customer_id',\n        'event_id',\n        'event_name',\n        'timestamp'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      customer_id: {
        type: 'string',
        description: 'Filter events by customer ID',
      },
      end: {
        type: 'string',
        description: 'Filter events created before this timestamp',
        format: 'date-time',
      },
      event_name: {
        type: 'string',
        description:
          "Filter events by event name. If both event_name and meter_id are provided, they must match the meter's configured event_name",
      },
      meter_id: {
        type: 'string',
        description:
          "Filter events by meter ID. When provided, only events that match the meter's event_name and filter criteria will be returned",
      },
      page_number: {
        type: 'integer',
        description: 'Page number (0-based, default: 0)',
      },
      page_size: {
        type: 'integer',
        description: 'Number of events to return per page (default: 10)',
      },
      start: {
        type: 'string',
        description: 'Filter events created after this timestamp',
        format: 'date-time',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: [],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter, ...body } = args as any;
  const response = await client.usageEvents.list(body).asResponse();
  return asTextContentResult(await maybeFilter(jq_filter, await response.json()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/webhooks/create-webhooks.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'webhooks',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/webhooks',
  operationId: 'create_webhook',
};

export const tool: Tool = {
  name: 'create_webhooks',
  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 a new webhook\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/webhook_details',\n  $defs: {\n    webhook_details: {\n      type: 'object',\n      title: 'Webhook Details',\n      properties: {\n        id: {\n          type: 'string',\n          description: 'The webhook\\'s ID.'\n        },\n        created_at: {\n          type: 'string',\n          description: 'Created at timestamp'\n        },\n        description: {\n          type: 'string',\n          description: 'An example webhook name.'\n        },\n        metadata: {\n          type: 'object',\n          description: 'Metadata of the webhook',\n          additionalProperties: true\n        },\n        updated_at: {\n          type: 'string',\n          description: 'Updated at timestamp'\n        },\n        url: {\n          type: 'string',\n          description: 'Url endpoint of the webhook'\n        },\n        disabled: {\n          type: 'boolean',\n          description: 'Status of the webhook.\\n\\nIf true, events are not sent'\n        },\n        filter_types: {\n          type: 'array',\n          description: 'Filter events to the webhook.\\n\\nWebhook event will only be sent for events in the list.',\n          items: {\n            type: 'string'\n          }\n        },\n        rate_limit: {\n          type: 'integer',\n          description: 'Configured rate limit'\n        }\n      },\n      required: [        'id',\n        'created_at',\n        'description',\n        'metadata',\n        'updated_at',\n        'url'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      url: {
        type: 'string',
        description: 'Url of the webhook',
      },
      description: {
        type: 'string',
      },
      disabled: {
        type: 'boolean',
        description: 'Create the webhook in a disabled state.\n\nDefault is false',
      },
      filter_types: {
        type: 'array',
        description:
          'Filter events to the webhook.\n\nWebhook event will only be sent for events in the list.',
        items: {
          $ref: '#/$defs/webhook_event_type',
        },
      },
      headers: {
        type: 'object',
        description: 'Custom headers to be passed',
        additionalProperties: true,
      },
      idempotency_key: {
        type: 'string',
        description: "The request's idempotency key",
      },
      metadata: {
        type: 'object',
        description: 'Metadata to be passed to the webhook\nDefaut is {}',
        additionalProperties: true,
      },
      rate_limit: {
        type: 'integer',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['url'],
    $defs: {
      webhook_event_type: {
        type: 'string',
        description: 'Event types for Dodo events',
        enum: [
          'payment.succeeded',
          'payment.failed',
          'payment.processing',
          'payment.cancelled',
          'refund.succeeded',
          'refund.failed',
          'dispute.opened',
          'dispute.expired',
          'dispute.accepted',
          'dispute.cancelled',
          'dispute.challenged',
          'dispute.won',
          'dispute.lost',
          'subscription.active',
          'subscription.renewed',
          'subscription.on_hold',
          'subscription.cancelled',
          'subscription.failed',
          'subscription.expired',
          'subscription.plan_changed',
          'license_key.created',
        ],
      },
    },
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.webhooks.create(body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/internal/to-file.ts:
--------------------------------------------------------------------------------

```typescript
import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads';
import type { FilePropertyBag } from './builtin-types';
import { checkFileSupport } from './uploads';

type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView;

/**
 * Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc.
 * Don't add arrayBuffer here, node-fetch doesn't have it
 */
interface BlobLike {
  /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */
  readonly size: number;
  /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */
  readonly type: string;
  /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */
  text(): Promise<string>;
  /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */
  slice(start?: number, end?: number): BlobLike;
}

/**
 * This check adds the arrayBuffer() method type because it is available and used at runtime
 */
const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise<ArrayBuffer> } =>
  value != null &&
  typeof value === 'object' &&
  typeof value.size === 'number' &&
  typeof value.type === 'string' &&
  typeof value.text === 'function' &&
  typeof value.slice === 'function' &&
  typeof value.arrayBuffer === 'function';

/**
 * Intended to match DOM File, node:buffer File, undici File, etc.
 */
interface FileLike extends BlobLike {
  /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */
  readonly lastModified: number;
  /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */
  readonly name?: string | undefined;
}

/**
 * This check adds the arrayBuffer() method type because it is available and used at runtime
 */
const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise<ArrayBuffer> } =>
  value != null &&
  typeof value === 'object' &&
  typeof value.name === 'string' &&
  typeof value.lastModified === 'number' &&
  isBlobLike(value);

/**
 * Intended to match DOM Response, node-fetch Response, undici Response, etc.
 */
export interface ResponseLike {
  url: string;
  blob(): Promise<BlobLike>;
}

const isResponseLike = (value: any): value is ResponseLike =>
  value != null &&
  typeof value === 'object' &&
  typeof value.url === 'string' &&
  typeof value.blob === 'function';

export type ToFileInput =
  | FileLike
  | ResponseLike
  | Exclude<BlobLikePart, string>
  | AsyncIterable<BlobLikePart>;

/**
 * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
 * @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts
 * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
 * @param {Object=} options additional properties
 * @param {string=} options.type the MIME type of the content
 * @param {number=} options.lastModified the last modified timestamp
 * @returns a {@link File} with the given properties
 */
export async function toFile(
  value: ToFileInput | PromiseLike<ToFileInput>,
  name?: string | null | undefined,
  options?: FilePropertyBag | undefined,
): Promise<File> {
  checkFileSupport();

  // If it's a promise, resolve it.
  value = await value;

  // If we've been given a `File` we don't need to do anything
  if (isFileLike(value)) {
    if (value instanceof File) {
      return value;
    }
    return makeFile([await value.arrayBuffer()], value.name);
  }

  if (isResponseLike(value)) {
    const blob = await value.blob();
    name ||= new URL(value.url).pathname.split(/[\\/]/).pop();

    return makeFile(await getBytes(blob), name, options);
  }

  const parts = await getBytes(value);

  name ||= getName(value);

  if (!options?.type) {
    const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type);
    if (typeof type === 'string') {
      options = { ...options, type };
    }
  }

  return makeFile(parts, name, options);
}

async function getBytes(value: BlobLikePart | AsyncIterable<BlobLikePart>): Promise<Array<BlobPart>> {
  let parts: Array<BlobPart> = [];
  if (
    typeof value === 'string' ||
    ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc.
    value instanceof ArrayBuffer
  ) {
    parts.push(value);
  } else if (isBlobLike(value)) {
    parts.push(value instanceof Blob ? value : await value.arrayBuffer());
  } else if (
    isAsyncIterable(value) // includes Readable, ReadableStream, etc.
  ) {
    for await (const chunk of value) {
      parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating?
    }
  } else {
    const constructor = value?.constructor?.name;
    throw new Error(
      `Unexpected data type: ${typeof value}${
        constructor ? `; constructor: ${constructor}` : ''
      }${propsForError(value)}`,
    );
  }

  return parts;
}

function propsForError(value: unknown): string {
  if (typeof value !== 'object' || value === null) return '';
  const props = Object.getOwnPropertyNames(value);
  return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`;
}

```

--------------------------------------------------------------------------------
/src/resources/disputes.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../core/resource';
import * as PaymentsAPI from './payments';
import { APIPromise } from '../core/api-promise';
import {
  DefaultPageNumberPagination,
  type DefaultPageNumberPaginationParams,
  PagePromise,
} from '../core/pagination';
import { RequestOptions } from '../internal/request-options';
import { path } from '../internal/utils/path';

export class Disputes extends APIResource {
  retrieve(disputeID: string, options?: RequestOptions): APIPromise<GetDispute> {
    return this._client.get(path`/disputes/${disputeID}`, options);
  }

  list(
    query: DisputeListParams | null | undefined = {},
    options?: RequestOptions,
  ): PagePromise<DisputeListResponsesDefaultPageNumberPagination, DisputeListResponse> {
    return this._client.getAPIList('/disputes', DefaultPageNumberPagination<DisputeListResponse>, {
      query,
      ...options,
    });
  }
}

export type DisputeListResponsesDefaultPageNumberPagination =
  DefaultPageNumberPagination<DisputeListResponse>;

export interface Dispute {
  /**
   * The amount involved in the dispute, represented as a string to accommodate
   * precision.
   */
  amount: string;

  /**
   * The unique identifier of the business involved in the dispute.
   */
  business_id: string;

  /**
   * The timestamp of when the dispute was created, in UTC.
   */
  created_at: string;

  /**
   * The currency of the disputed amount, represented as an ISO 4217 currency code.
   */
  currency: string;

  /**
   * The unique identifier of the dispute.
   */
  dispute_id: string;

  /**
   * The current stage of the dispute process.
   */
  dispute_stage: DisputeStage;

  /**
   * The current status of the dispute.
   */
  dispute_status: DisputeStatus;

  /**
   * The unique identifier of the payment associated with the dispute.
   */
  payment_id: string;

  /**
   * Remarks
   */
  remarks?: string | null;
}

export type DisputeStage = 'pre_dispute' | 'dispute' | 'pre_arbitration';

export type DisputeStatus =
  | 'dispute_opened'
  | 'dispute_expired'
  | 'dispute_accepted'
  | 'dispute_cancelled'
  | 'dispute_challenged'
  | 'dispute_won'
  | 'dispute_lost';

export interface GetDispute {
  /**
   * The amount involved in the dispute, represented as a string to accommodate
   * precision.
   */
  amount: string;

  /**
   * The unique identifier of the business involved in the dispute.
   */
  business_id: string;

  /**
   * The timestamp of when the dispute was created, in UTC.
   */
  created_at: string;

  /**
   * The currency of the disputed amount, represented as an ISO 4217 currency code.
   */
  currency: string;

  /**
   * The customer who filed the dispute
   */
  customer: PaymentsAPI.CustomerLimitedDetails;

  /**
   * The unique identifier of the dispute.
   */
  dispute_id: string;

  /**
   * The current stage of the dispute process.
   */
  dispute_stage: DisputeStage;

  /**
   * The current status of the dispute.
   */
  dispute_status: DisputeStatus;

  /**
   * The unique identifier of the payment associated with the dispute.
   */
  payment_id: string;

  /**
   * Reason for the dispute
   */
  reason?: string | null;

  /**
   * Remarks
   */
  remarks?: string | null;
}

export interface DisputeListResponse {
  /**
   * The amount involved in the dispute, represented as a string to accommodate
   * precision.
   */
  amount: string;

  /**
   * The unique identifier of the business involved in the dispute.
   */
  business_id: string;

  /**
   * The timestamp of when the dispute was created, in UTC.
   */
  created_at: string;

  /**
   * The currency of the disputed amount, represented as an ISO 4217 currency code.
   */
  currency: string;

  /**
   * The unique identifier of the dispute.
   */
  dispute_id: string;

  /**
   * The current stage of the dispute process.
   */
  dispute_stage: DisputeStage;

  /**
   * The current status of the dispute.
   */
  dispute_status: DisputeStatus;

  /**
   * The unique identifier of the payment associated with the dispute.
   */
  payment_id: string;
}

export interface DisputeListParams extends DefaultPageNumberPaginationParams {
  /**
   * Get events after this created time
   */
  created_at_gte?: string;

  /**
   * Get events created before this time
   */
  created_at_lte?: string;

  /**
   * Filter by customer_id
   */
  customer_id?: string;

  /**
   * Filter by dispute stage
   */
  dispute_stage?: 'pre_dispute' | 'dispute' | 'pre_arbitration';

  /**
   * Filter by dispute status
   */
  dispute_status?:
    | 'dispute_opened'
    | 'dispute_expired'
    | 'dispute_accepted'
    | 'dispute_cancelled'
    | 'dispute_challenged'
    | 'dispute_won'
    | 'dispute_lost';
}

export declare namespace Disputes {
  export {
    type Dispute as Dispute,
    type DisputeStage as DisputeStage,
    type DisputeStatus as DisputeStatus,
    type GetDispute as GetDispute,
    type DisputeListResponse as DisputeListResponse,
    type DisputeListResponsesDefaultPageNumberPagination as DisputeListResponsesDefaultPageNumberPagination,
    type DisputeListParams as DisputeListParams,
  };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/payments/retrieve-line-items-payments.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'payments',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/payments/{payment_id}/line-items',
  operationId: 'get_payment_line_items_handler',
};

export const tool: Tool = {
  name: 'retrieve_line_items_payments',
  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\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/payment_retrieve_line_items_response',\n  $defs: {\n    payment_retrieve_line_items_response: {\n      type: 'object',\n      properties: {\n        currency: {\n          $ref: '#/$defs/currency'\n        },\n        items: {\n          type: 'array',\n          items: {\n            type: 'object',\n            properties: {\n              amount: {\n                type: 'integer'\n              },\n              items_id: {\n                type: 'string'\n              },\n              refundable_amount: {\n                type: 'integer'\n              },\n              tax: {\n                type: 'integer'\n              },\n              description: {\n                type: 'string'\n              },\n              name: {\n                type: 'string'\n              }\n            },\n            required: [              'amount',\n              'items_id',\n              'refundable_amount',\n              'tax'\n            ]\n          }\n        }\n      },\n      required: [        'currency',\n        'items'\n      ]\n    },\n    currency: {\n      type: 'string',\n      enum: [        'AED',\n        'ALL',\n        'AMD',\n        'ANG',\n        'AOA',\n        'ARS',\n        'AUD',\n        'AWG',\n        'AZN',\n        'BAM',\n        'BBD',\n        'BDT',\n        'BGN',\n        'BHD',\n        'BIF',\n        'BMD',\n        'BND',\n        'BOB',\n        'BRL',\n        'BSD',\n        'BWP',\n        'BYN',\n        'BZD',\n        'CAD',\n        'CHF',\n        'CLP',\n        'CNY',\n        'COP',\n        'CRC',\n        'CUP',\n        'CVE',\n        'CZK',\n        'DJF',\n        'DKK',\n        'DOP',\n        'DZD',\n        'EGP',\n        'ETB',\n        'EUR',\n        'FJD',\n        'FKP',\n        'GBP',\n        'GEL',\n        'GHS',\n        'GIP',\n        'GMD',\n        'GNF',\n        'GTQ',\n        'GYD',\n        'HKD',\n        'HNL',\n        'HRK',\n        'HTG',\n        'HUF',\n        'IDR',\n        'ILS',\n        'INR',\n        'IQD',\n        'JMD',\n        'JOD',\n        'JPY',\n        'KES',\n        'KGS',\n        'KHR',\n        'KMF',\n        'KRW',\n        'KWD',\n        'KYD',\n        'KZT',\n        'LAK',\n        'LBP',\n        'LKR',\n        'LRD',\n        'LSL',\n        'LYD',\n        'MAD',\n        'MDL',\n        'MGA',\n        'MKD',\n        'MMK',\n        'MNT',\n        'MOP',\n        'MRU',\n        'MUR',\n        'MVR',\n        'MWK',\n        'MXN',\n        'MYR',\n        'MZN',\n        'NAD',\n        'NGN',\n        'NIO',\n        'NOK',\n        'NPR',\n        'NZD',\n        'OMR',\n        'PAB',\n        'PEN',\n        'PGK',\n        'PHP',\n        'PKR',\n        'PLN',\n        'PYG',\n        'QAR',\n        'RON',\n        'RSD',\n        'RUB',\n        'RWF',\n        'SAR',\n        'SBD',\n        'SCR',\n        'SEK',\n        'SGD',\n        'SHP',\n        'SLE',\n        'SLL',\n        'SOS',\n        'SRD',\n        'SSP',\n        'STN',\n        'SVC',\n        'SZL',\n        'THB',\n        'TND',\n        'TOP',\n        'TRY',\n        'TTD',\n        'TWD',\n        'TZS',\n        'UAH',\n        'UGX',\n        'USD',\n        'UYU',\n        'UZS',\n        'VES',\n        'VND',\n        'VUV',\n        'WST',\n        'XAF',\n        'XCD',\n        'XOF',\n        'XPF',\n        'YER',\n        'ZAR',\n        'ZMW'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      payment_id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['payment_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { payment_id, jq_filter, ...body } = args as any;
  return asTextContentResult(
    await maybeFilter(jq_filter, await client.payments.retrieveLineItems(payment_id)),
  );
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/customers/wallets/list-customers-wallets.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'customers.wallets',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/customers/{customer_id}/wallets',
  operationId: 'get_customer_wallets',
};

export const tool: Tool = {
  name: 'list_customers_wallets',
  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\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/wallet_list_response',\n  $defs: {\n    wallet_list_response: {\n      type: 'object',\n      properties: {\n        items: {\n          type: 'array',\n          items: {\n            $ref: '#/$defs/customer_wallet'\n          }\n        },\n        total_balance_usd: {\n          type: 'integer',\n          description: 'Sum of all wallet balances converted to USD (in smallest unit)'\n        }\n      },\n      required: [        'items',\n        'total_balance_usd'\n      ]\n    },\n    customer_wallet: {\n      type: 'object',\n      properties: {\n        balance: {\n          type: 'integer'\n        },\n        created_at: {\n          type: 'string',\n          format: 'date-time'\n        },\n        currency: {\n          $ref: '#/$defs/currency'\n        },\n        customer_id: {\n          type: 'string'\n        },\n        updated_at: {\n          type: 'string',\n          format: 'date-time'\n        }\n      },\n      required: [        'balance',\n        'created_at',\n        'currency',\n        'customer_id',\n        'updated_at'\n      ]\n    },\n    currency: {\n      type: 'string',\n      enum: [        'AED',\n        'ALL',\n        'AMD',\n        'ANG',\n        'AOA',\n        'ARS',\n        'AUD',\n        'AWG',\n        'AZN',\n        'BAM',\n        'BBD',\n        'BDT',\n        'BGN',\n        'BHD',\n        'BIF',\n        'BMD',\n        'BND',\n        'BOB',\n        'BRL',\n        'BSD',\n        'BWP',\n        'BYN',\n        'BZD',\n        'CAD',\n        'CHF',\n        'CLP',\n        'CNY',\n        'COP',\n        'CRC',\n        'CUP',\n        'CVE',\n        'CZK',\n        'DJF',\n        'DKK',\n        'DOP',\n        'DZD',\n        'EGP',\n        'ETB',\n        'EUR',\n        'FJD',\n        'FKP',\n        'GBP',\n        'GEL',\n        'GHS',\n        'GIP',\n        'GMD',\n        'GNF',\n        'GTQ',\n        'GYD',\n        'HKD',\n        'HNL',\n        'HRK',\n        'HTG',\n        'HUF',\n        'IDR',\n        'ILS',\n        'INR',\n        'IQD',\n        'JMD',\n        'JOD',\n        'JPY',\n        'KES',\n        'KGS',\n        'KHR',\n        'KMF',\n        'KRW',\n        'KWD',\n        'KYD',\n        'KZT',\n        'LAK',\n        'LBP',\n        'LKR',\n        'LRD',\n        'LSL',\n        'LYD',\n        'MAD',\n        'MDL',\n        'MGA',\n        'MKD',\n        'MMK',\n        'MNT',\n        'MOP',\n        'MRU',\n        'MUR',\n        'MVR',\n        'MWK',\n        'MXN',\n        'MYR',\n        'MZN',\n        'NAD',\n        'NGN',\n        'NIO',\n        'NOK',\n        'NPR',\n        'NZD',\n        'OMR',\n        'PAB',\n        'PEN',\n        'PGK',\n        'PHP',\n        'PKR',\n        'PLN',\n        'PYG',\n        'QAR',\n        'RON',\n        'RSD',\n        'RUB',\n        'RWF',\n        'SAR',\n        'SBD',\n        'SCR',\n        'SEK',\n        'SGD',\n        'SHP',\n        'SLE',\n        'SLL',\n        'SOS',\n        'SRD',\n        'SSP',\n        'STN',\n        'SVC',\n        'SZL',\n        'THB',\n        'TND',\n        'TOP',\n        'TRY',\n        'TTD',\n        'TWD',\n        'TZS',\n        'UAH',\n        'UGX',\n        'USD',\n        'UYU',\n        'UZS',\n        'VES',\n        'VND',\n        'VUV',\n        'WST',\n        'XAF',\n        'XCD',\n        'XOF',\n        'XPF',\n        'YER',\n        'ZAR',\n        'ZMW'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      customer_id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['customer_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { customer_id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.customers.wallets.list(customer_id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/core/pagination.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { DodoPaymentsError } from './error';
import { FinalRequestOptions } from '../internal/request-options';
import { defaultParseResponse } from '../internal/parse';
import { type DodoPayments } from '../client';
import { APIPromise } from './api-promise';
import { type APIResponseProps } from '../internal/parse';
import { maybeObj } from '../internal/utils/values';

export type PageRequestOptions = Pick<FinalRequestOptions, 'query' | 'headers' | 'body' | 'path' | 'method'>;

export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
  #client: DodoPayments;
  protected options: FinalRequestOptions;

  protected response: Response;
  protected body: unknown;

  constructor(client: DodoPayments, response: Response, body: unknown, options: FinalRequestOptions) {
    this.#client = client;
    this.options = options;
    this.response = response;
    this.body = body;
  }

  abstract nextPageRequestOptions(): PageRequestOptions | null;

  abstract getPaginatedItems(): Item[];

  hasNextPage(): boolean {
    const items = this.getPaginatedItems();
    if (!items.length) return false;
    return this.nextPageRequestOptions() != null;
  }

  async getNextPage(): Promise<this> {
    const nextOptions = this.nextPageRequestOptions();
    if (!nextOptions) {
      throw new DodoPaymentsError(
        'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.',
      );
    }

    return await this.#client.requestAPIList(this.constructor as any, nextOptions);
  }

  async *iterPages(): AsyncGenerator<this> {
    let page: this = this;
    yield page;
    while (page.hasNextPage()) {
      page = await page.getNextPage();
      yield page;
    }
  }

  async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
    for await (const page of this.iterPages()) {
      for (const item of page.getPaginatedItems()) {
        yield item;
      }
    }
  }
}

/**
 * This subclass of Promise will resolve to an instantiated Page once the request completes.
 *
 * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg:
 *
 *    for await (const item of client.items.list()) {
 *      console.log(item)
 *    }
 */
export class PagePromise<
    PageClass extends AbstractPage<Item>,
    Item = ReturnType<PageClass['getPaginatedItems']>[number],
  >
  extends APIPromise<PageClass>
  implements AsyncIterable<Item>
{
  constructor(
    client: DodoPayments,
    request: Promise<APIResponseProps>,
    Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass,
  ) {
    super(
      client,
      request,
      async (client, props) =>
        new Page(client, props.response, await defaultParseResponse(client, props), props.options),
    );
  }

  /**
   * Allow auto-paginating iteration on an unawaited list call, eg:
   *
   *    for await (const item of client.items.list()) {
   *      console.log(item)
   *    }
   */
  async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
    const page = await this;
    for await (const item of page) {
      yield item;
    }
  }
}

export interface DefaultPageNumberPaginationResponse<Item> {
  items: Array<Item>;
}

export interface DefaultPageNumberPaginationParams {
  page_number?: number;

  page_size?: number;
}

export class DefaultPageNumberPagination<Item>
  extends AbstractPage<Item>
  implements DefaultPageNumberPaginationResponse<Item>
{
  items: Array<Item>;

  constructor(
    client: DodoPayments,
    response: Response,
    body: DefaultPageNumberPaginationResponse<Item>,
    options: FinalRequestOptions,
  ) {
    super(client, response, body, options);

    this.items = body.items || [];
  }

  getPaginatedItems(): Item[] {
    return this.items ?? [];
  }

  nextPageRequestOptions(): PageRequestOptions | null {
    const query = this.options.query as DefaultPageNumberPaginationParams;
    const currentPage = query?.page_number ?? 1;

    return {
      ...this.options,
      query: {
        ...maybeObj(this.options.query),
        page_number: currentPage + 1,
      },
    };
  }
}

export interface CursorPagePaginationResponse<Item> {
  data: Array<Item>;

  iterator: string;

  done: boolean;
}

export interface CursorPagePaginationParams {
  iterator?: string;

  limit?: number;
}

export class CursorPagePagination<Item>
  extends AbstractPage<Item>
  implements CursorPagePaginationResponse<Item>
{
  data: Array<Item>;

  iterator: string;

  done: boolean;

  constructor(
    client: DodoPayments,
    response: Response,
    body: CursorPagePaginationResponse<Item>,
    options: FinalRequestOptions,
  ) {
    super(client, response, body, options);

    this.data = body.data || [];
    this.iterator = body.iterator || '';
    this.done = body.done || false;
  }

  getPaginatedItems(): Item[] {
    return this.data ?? [];
  }

  override hasNextPage(): boolean {
    if (this.done === false) {
      return false;
    }

    return super.hasNextPage();
  }

  nextPageRequestOptions(): PageRequestOptions | null {
    const cursor = this.iterator;
    if (!cursor) {
      return null;
    }

    return {
      ...this.options,
      query: {
        ...maybeObj(this.options.query),
        iterator: cursor,
      },
    };
  }
}

```

--------------------------------------------------------------------------------
/tests/api-resources/products/products.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource products', () => {
  test('create: only required params', async () => {
    const responsePromise = client.products.create({
      price: {
        currency: 'AED',
        discount: 0,
        price: 0,
        purchasing_power_parity: true,
        type: 'one_time_price',
      },
      tax_category: 'digital_products',
    });
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('create: required and optional params', async () => {
    const response = await client.products.create({
      price: {
        currency: 'AED',
        discount: 0,
        price: 0,
        purchasing_power_parity: true,
        type: 'one_time_price',
        pay_what_you_want: true,
        suggested_price: 0,
        tax_inclusive: true,
      },
      tax_category: 'digital_products',
      addons: ['string'],
      brand_id: 'brand_id',
      description: 'description',
      digital_product_delivery: { external_url: 'external_url', instructions: 'instructions' },
      license_key_activation_message: 'license_key_activation_message',
      license_key_activations_limit: 0,
      license_key_duration: { count: 0, interval: 'Day' },
      license_key_enabled: true,
      metadata: { foo: 'string' },
      name: 'name',
    });
  });

  test('retrieve', async () => {
    const responsePromise = client.products.retrieve('id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('update', async () => {
    const responsePromise = client.products.update('id', {});
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list', async () => {
    const responsePromise = client.products.list();
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list: request options and params are passed correctly', async () => {
    // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
    await expect(
      client.products.list(
        { archived: true, brand_id: 'brand_id', page_number: 0, page_size: 0, recurring: true },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });

  test('archive', async () => {
    const responsePromise = client.products.archive('id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('unarchive', async () => {
    const responsePromise = client.products.unarchive('id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('updateFiles: only required params', async () => {
    const responsePromise = client.products.updateFiles('id', { file_name: 'file_name' });
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('updateFiles: required and optional params', async () => {
    const response = await client.products.updateFiles('id', { file_name: 'file_name' });
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/discounts/update-discounts.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'discounts',
  operation: 'write',
  tags: [],
  httpMethod: 'patch',
  httpPath: '/discounts/{discount_id}',
  operationId: 'patch_discount_handler',
};

export const tool: Tool = {
  name: 'update_discounts',
  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\nPATCH /discounts/{discount_id}\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/discount',\n  $defs: {\n    discount: {\n      type: 'object',\n      properties: {\n        amount: {\n          type: 'integer',\n          description: 'The discount amount.\\n\\n- If `discount_type` is `percentage`, this is in **basis points**\\n  (e.g., 540 => 5.4%).\\n- Otherwise, this is **USD cents** (e.g., 100 => `$1.00`).'\n        },\n        business_id: {\n          type: 'string',\n          description: 'The business this discount belongs to.'\n        },\n        code: {\n          type: 'string',\n          description: 'The discount code (up to 16 chars).'\n        },\n        created_at: {\n          type: 'string',\n          description: 'Timestamp when the discount is created',\n          format: 'date-time'\n        },\n        discount_id: {\n          type: 'string',\n          description: 'The unique discount ID'\n        },\n        restricted_to: {\n          type: 'array',\n          description: 'List of product IDs to which this discount is restricted.',\n          items: {\n            type: 'string'\n          }\n        },\n        times_used: {\n          type: 'integer',\n          description: 'How many times this discount has been used.'\n        },\n        type: {\n          $ref: '#/$defs/discount_type'\n        },\n        expires_at: {\n          type: 'string',\n          description: 'Optional date/time after which discount is expired.',\n          format: 'date-time'\n        },\n        name: {\n          type: 'string',\n          description: 'Name for the Discount'\n        },\n        subscription_cycles: {\n          type: 'integer',\n          description: 'Number of subscription billing cycles this discount is valid for.\\nIf not provided, the discount will be applied indefinitely to\\nall recurring payments related to the subscription.'\n        },\n        usage_limit: {\n          type: 'integer',\n          description: 'Usage limit for this discount, if any.'\n        }\n      },\n      required: [        'amount',\n        'business_id',\n        'code',\n        'created_at',\n        'discount_id',\n        'restricted_to',\n        'times_used',\n        'type'\n      ]\n    },\n    discount_type: {\n      type: 'string',\n      enum: [        'percentage'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      discount_id: {
        type: 'string',
      },
      amount: {
        type: 'integer',
        description:
          'If present, update the discount amount:\n- If `discount_type` is `percentage`, this represents **basis points** (e.g., `540` = `5.4%`).\n- Otherwise, this represents **USD cents** (e.g., `100` = `$1.00`).\n\nMust be at least 1 if provided.',
      },
      code: {
        type: 'string',
        description: 'If present, update the discount code (uppercase).',
      },
      expires_at: {
        type: 'string',
        format: 'date-time',
      },
      name: {
        type: 'string',
      },
      restricted_to: {
        type: 'array',
        description:
          'If present, replaces all restricted product IDs with this new set.\nTo remove all restrictions, send empty array',
        items: {
          type: 'string',
        },
      },
      subscription_cycles: {
        type: 'integer',
        description:
          'Number of subscription billing cycles this discount is valid for.\nIf not provided, the discount will be applied indefinitely to\nall recurring payments related to the subscription.',
      },
      type: {
        $ref: '#/$defs/discount_type',
      },
      usage_limit: {
        type: 'integer',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['discount_id'],
    $defs: {
      discount_type: {
        type: 'string',
        enum: ['percentage'],
      },
    },
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { discount_id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.update(discount_id, body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/dynamic-tools.ts:
--------------------------------------------------------------------------------

```typescript
import DodoPayments from 'dodopayments';
import { Endpoint, asTextContentResult, ToolCallResult } from './tools/types';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { z } from 'zod';
import { Cabidela } from '@cloudflare/cabidela';

function zodToInputSchema(schema: z.ZodSchema) {
  return {
    type: 'object' as const,
    ...(zodToJsonSchema(schema) as any),
  };
}

/**
 * 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,
 * 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.
 *
 * @param endpoints - The endpoints to include in the list.
 */
export function dynamicTools(endpoints: Endpoint[]): Endpoint[] {
  const listEndpointsSchema = z.object({
    search_query: z
      .string()
      .optional()
      .describe(
        'An optional search query to filter the endpoints by. Provide a partial name, resource, operation, or tag to filter the endpoints returned.',
      ),
  });

  const listEndpointsTool = {
    metadata: {
      resource: 'dynamic_tools',
      operation: 'read' as const,
      tags: [],
    },
    tool: {
      name: 'list_api_endpoints',
      description: 'List or search for all endpoints in the Dodo Payments TypeScript API',
      inputSchema: zodToInputSchema(listEndpointsSchema),
    },
    handler: async (
      client: DodoPayments,
      args: Record<string, unknown> | undefined,
    ): Promise<ToolCallResult> => {
      const query = args && listEndpointsSchema.parse(args).search_query?.trim();

      const filteredEndpoints =
        query && query.length > 0 ?
          endpoints.filter((endpoint) => {
            const fieldsToMatch = [
              endpoint.tool.name,
              endpoint.tool.description,
              endpoint.metadata.resource,
              endpoint.metadata.operation,
              ...endpoint.metadata.tags,
            ];
            return fieldsToMatch.some((field) => field && field.toLowerCase().includes(query.toLowerCase()));
          })
        : endpoints;

      return asTextContentResult({
        tools: filteredEndpoints.map(({ tool, metadata }) => ({
          name: tool.name,
          description: tool.description,
          resource: metadata.resource,
          operation: metadata.operation,
          tags: metadata.tags,
        })),
      });
    },
  };

  const getEndpointSchema = z.object({
    endpoint: z.string().describe('The name of the endpoint to get the schema for.'),
  });
  const getEndpointTool = {
    metadata: {
      resource: 'dynamic_tools',
      operation: 'read' as const,
      tags: [],
    },
    tool: {
      name: 'get_api_endpoint_schema',
      description:
        'Get the schema for an endpoint in the Dodo Payments TypeScript API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.',
      inputSchema: zodToInputSchema(getEndpointSchema),
    },
    handler: async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
      if (!args) {
        throw new Error('No endpoint provided');
      }
      const endpointName = getEndpointSchema.parse(args).endpoint;

      const endpoint = endpoints.find((e) => e.tool.name === endpointName);
      if (!endpoint) {
        throw new Error(`Endpoint ${endpointName} not found`);
      }
      return asTextContentResult(endpoint.tool);
    },
  };

  const invokeEndpointSchema = z.object({
    endpoint_name: z.string().describe('The name of the endpoint to invoke.'),
    args: z
      .record(z.string(), z.any())
      .describe(
        'The arguments to pass to the endpoint. This must match the schema returned by the `get_api_endpoint_schema` tool.',
      ),
  });

  const invokeEndpointTool = {
    metadata: {
      resource: 'dynamic_tools',
      operation: 'write' as const,
      tags: [],
    },
    tool: {
      name: 'invoke_api_endpoint',
      description:
        'Invoke an endpoint in the Dodo Payments TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.',
      inputSchema: zodToInputSchema(invokeEndpointSchema),
    },
    handler: async (
      client: DodoPayments,
      args: Record<string, unknown> | undefined,
    ): Promise<ToolCallResult> => {
      if (!args) {
        throw new Error('No endpoint provided');
      }
      const { success, data, error } = invokeEndpointSchema.safeParse(args);
      if (!success) {
        throw new Error(`Invalid arguments for endpoint. ${error?.format()}`);
      }
      const { endpoint_name, args: endpointArgs } = data;

      const endpoint = endpoints.find((e) => e.tool.name === endpoint_name);
      if (!endpoint) {
        throw new Error(
          `Endpoint ${endpoint_name} not found. Use the \`list_api_endpoints\` tool to get the list of available endpoints.`,
        );
      }

      try {
        // Try to validate the arguments for a better error message
        const cabidela = new Cabidela(endpoint.tool.inputSchema, { fullErrors: true });
        cabidela.validate(endpointArgs);
      } catch (error) {
        throw new Error(`Invalid arguments for endpoint ${endpoint_name}:\n${error}`);
      }

      return await endpoint.handler(client, endpointArgs);
    },
  };

  return [getEndpointTool, listEndpointsTool, invokeEndpointTool];
}

```

--------------------------------------------------------------------------------
/src/resources/index.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export {
  Addons,
  type AddonResponse,
  type AddonUpdateImagesResponse,
  type AddonCreateParams,
  type AddonUpdateParams,
  type AddonListParams,
  type AddonResponsesDefaultPageNumberPagination,
} from './addons';
export {
  Brands,
  type Brand,
  type BrandListResponse,
  type BrandUpdateImagesResponse,
  type BrandCreateParams,
  type BrandUpdateParams,
} from './brands';
export {
  CheckoutSessions,
  type CheckoutSessionRequest,
  type CheckoutSessionResponse,
  type CheckoutSessionStatus,
  type CheckoutSessionCreateParams,
} from './checkout-sessions';
export {
  Customers,
  type Customer,
  type CustomerPortalSession,
  type CustomerCreateParams,
  type CustomerUpdateParams,
  type CustomerListParams,
  type CustomersDefaultPageNumberPagination,
} from './customers/customers';
export {
  Discounts,
  type Discount,
  type DiscountType,
  type DiscountCreateParams,
  type DiscountUpdateParams,
  type DiscountListParams,
  type DiscountsDefaultPageNumberPagination,
} from './discounts';
export {
  Disputes,
  type Dispute,
  type DisputeStage,
  type DisputeStatus,
  type GetDispute,
  type DisputeListResponse,
  type DisputeListParams,
  type DisputeListResponsesDefaultPageNumberPagination,
} from './disputes';
export { Invoices } from './invoices/invoices';
export {
  LicenseKeyInstances,
  type LicenseKeyInstance,
  type LicenseKeyInstanceUpdateParams,
  type LicenseKeyInstanceListParams,
  type LicenseKeyInstancesDefaultPageNumberPagination,
} from './license-key-instances';
export {
  LicenseKeys,
  type LicenseKey,
  type LicenseKeyStatus,
  type LicenseKeyUpdateParams,
  type LicenseKeyListParams,
  type LicenseKeysDefaultPageNumberPagination,
} from './license-keys';
export {
  Licenses,
  type LicenseActivateResponse,
  type LicenseValidateResponse,
  type LicenseActivateParams,
  type LicenseDeactivateParams,
  type LicenseValidateParams,
} from './licenses';
export {
  Meters,
  type Meter,
  type MeterAggregation,
  type MeterFilter,
  type MeterCreateParams,
  type MeterListParams,
  type MetersDefaultPageNumberPagination,
} from './meters';
export {
  Misc,
  type CountryCode,
  type Currency,
  type TaxCategory,
  type MiscListSupportedCountriesResponse,
} from './misc';
export {
  Payments,
  type AttachExistingCustomer,
  type BillingAddress,
  type CreateNewCustomer,
  type CustomerLimitedDetails,
  type CustomerRequest,
  type IntentStatus,
  type NewCustomer,
  type OneTimeProductCartItem,
  type Payment,
  type PaymentMethodTypes,
  type PaymentCreateResponse,
  type PaymentListResponse,
  type PaymentRetrieveLineItemsResponse,
  type PaymentCreateParams,
  type PaymentListParams,
  type PaymentListResponsesDefaultPageNumberPagination,
} from './payments';
export {
  Payouts,
  type PayoutListResponse,
  type PayoutListParams,
  type PayoutListResponsesDefaultPageNumberPagination,
} from './payouts';
export {
  Products,
  type AddMeterToPrice,
  type LicenseKeyDuration,
  type Price,
  type Product,
  type ProductListResponse,
  type ProductUpdateFilesResponse,
  type ProductCreateParams,
  type ProductUpdateParams,
  type ProductListParams,
  type ProductUpdateFilesParams,
  type ProductListResponsesDefaultPageNumberPagination,
} from './products/products';
export {
  Refunds,
  type Refund,
  type RefundStatus,
  type RefundListResponse,
  type RefundCreateParams,
  type RefundListParams,
  type RefundListResponsesDefaultPageNumberPagination,
} from './refunds';
export {
  Subscriptions,
  type AddonCartResponseItem,
  type AttachAddon,
  type OnDemandSubscription,
  type Subscription,
  type SubscriptionStatus,
  type TimeInterval,
  type SubscriptionCreateResponse,
  type SubscriptionListResponse,
  type SubscriptionChargeResponse,
  type SubscriptionRetrieveUsageHistoryResponse,
  type SubscriptionCreateParams,
  type SubscriptionUpdateParams,
  type SubscriptionListParams,
  type SubscriptionChangePlanParams,
  type SubscriptionChargeParams,
  type SubscriptionRetrieveUsageHistoryParams,
  type SubscriptionListResponsesDefaultPageNumberPagination,
  type SubscriptionRetrieveUsageHistoryResponsesDefaultPageNumberPagination,
} from './subscriptions';
export {
  UsageEvents,
  type Event,
  type EventInput,
  type UsageEventIngestResponse,
  type UsageEventListParams,
  type UsageEventIngestParams,
  type EventsDefaultPageNumberPagination,
} from './usage-events';
export { WebhookEvents, type WebhookEventType, type WebhookPayload } from './webhook-events';
export {
  Webhooks,
  type WebhookDetails,
  type WebhookRetrieveSecretResponse,
  type DisputeAcceptedWebhookEvent,
  type DisputeCancelledWebhookEvent,
  type DisputeChallengedWebhookEvent,
  type DisputeExpiredWebhookEvent,
  type DisputeLostWebhookEvent,
  type DisputeOpenedWebhookEvent,
  type DisputeWonWebhookEvent,
  type LicenseKeyCreatedWebhookEvent,
  type PaymentCancelledWebhookEvent,
  type PaymentFailedWebhookEvent,
  type PaymentProcessingWebhookEvent,
  type PaymentSucceededWebhookEvent,
  type RefundFailedWebhookEvent,
  type RefundSucceededWebhookEvent,
  type SubscriptionActiveWebhookEvent,
  type SubscriptionCancelledWebhookEvent,
  type SubscriptionExpiredWebhookEvent,
  type SubscriptionFailedWebhookEvent,
  type SubscriptionOnHoldWebhookEvent,
  type SubscriptionPlanChangedWebhookEvent,
  type SubscriptionRenewedWebhookEvent,
  type UnsafeUnwrapWebhookEvent,
  type UnwrapWebhookEvent,
  type WebhookCreateParams,
  type WebhookUpdateParams,
  type WebhookListParams,
  type WebhookDetailsCursorPagePagination,
} from './webhooks/webhooks';

```

--------------------------------------------------------------------------------
/tests/api-resources/webhooks/webhooks.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Webhook } from 'standardwebhooks';

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource webhooks', () => {
  test('create: only required params', async () => {
    const responsePromise = client.webhooks.create({ url: 'url' });
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('create: required and optional params', async () => {
    const response = await client.webhooks.create({
      url: 'url',
      description: 'description',
      disabled: true,
      filter_types: ['payment.succeeded'],
      headers: { foo: 'string' },
      idempotency_key: 'idempotency_key',
      metadata: { foo: 'string' },
      rate_limit: 0,
    });
  });

  test('retrieve', async () => {
    const responsePromise = client.webhooks.retrieve('webhook_id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('update', async () => {
    const responsePromise = client.webhooks.update('webhook_id', {});
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list', async () => {
    const responsePromise = client.webhooks.list();
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list: request options and params are passed correctly', async () => {
    // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
    await expect(
      client.webhooks.list({ iterator: 'iterator', limit: 0 }, { path: '/_stainless_unknown_path' }),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });

  test('delete', async () => {
    const responsePromise = client.webhooks.delete('webhook_id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('retrieveSecret', async () => {
    const responsePromise = client.webhooks.retrieveSecret('webhook_id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('unwrap', async () => {
    const key = 'whsec_c2VjcmV0Cg==';
    const payload =
      '{"business_id":"business_id","data":{"amount":"amount","business_id":"business_id","created_at":"2019-12-27T18:11:19.117Z","currency":"currency","dispute_id":"dispute_id","dispute_stage":"pre_dispute","dispute_status":"dispute_opened","payment_id":"payment_id","remarks":"remarks","payload_type":"Dispute"},"timestamp":"2019-12-27T18:11:19.117Z","type":"dispute.accepted"}';
    const msgID = '1';
    const timestamp = new Date();
    const wh = new Webhook(key);
    const signature = wh.sign(msgID, timestamp, payload);
    const headers: Record<string, string> = {
      'webhook-signature': signature,
      'webhook-id': msgID,
      'webhook-timestamp': String(Math.floor(timestamp.getTime() / 1000)),
    };
    client.webhooks.unwrap(payload, { headers, key });
    expect(() => {
      const wrongKey = 'whsec_aaaaaaaaaa==';
      client.webhooks.unwrap(payload, { headers, key: wrongKey });
    }).toThrow('No matching signature found');
    expect(() => {
      const badSig = wh.sign(msgID, timestamp, 'some other payload');
      client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-signature': badSig }, key });
    }).toThrow('No matching signature found');
    expect(() => {
      client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-timestamp': '5' }, key });
    }).toThrow('Message timestamp too old');
    expect(() => {
      client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-id': 'wrong' }, key });
    }).toThrow('No matching signature found');
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/discounts/create-discounts.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'discounts',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/discounts',
  operationId: 'create_discount_handler',
};

export const tool: Tool = {
  name: 'create_discounts',
  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\nPOST /discounts\nIf `code` is omitted or empty, a random 16-char uppercase code is generated.\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/discount',\n  $defs: {\n    discount: {\n      type: 'object',\n      properties: {\n        amount: {\n          type: 'integer',\n          description: 'The discount amount.\\n\\n- If `discount_type` is `percentage`, this is in **basis points**\\n  (e.g., 540 => 5.4%).\\n- Otherwise, this is **USD cents** (e.g., 100 => `$1.00`).'\n        },\n        business_id: {\n          type: 'string',\n          description: 'The business this discount belongs to.'\n        },\n        code: {\n          type: 'string',\n          description: 'The discount code (up to 16 chars).'\n        },\n        created_at: {\n          type: 'string',\n          description: 'Timestamp when the discount is created',\n          format: 'date-time'\n        },\n        discount_id: {\n          type: 'string',\n          description: 'The unique discount ID'\n        },\n        restricted_to: {\n          type: 'array',\n          description: 'List of product IDs to which this discount is restricted.',\n          items: {\n            type: 'string'\n          }\n        },\n        times_used: {\n          type: 'integer',\n          description: 'How many times this discount has been used.'\n        },\n        type: {\n          $ref: '#/$defs/discount_type'\n        },\n        expires_at: {\n          type: 'string',\n          description: 'Optional date/time after which discount is expired.',\n          format: 'date-time'\n        },\n        name: {\n          type: 'string',\n          description: 'Name for the Discount'\n        },\n        subscription_cycles: {\n          type: 'integer',\n          description: 'Number of subscription billing cycles this discount is valid for.\\nIf not provided, the discount will be applied indefinitely to\\nall recurring payments related to the subscription.'\n        },\n        usage_limit: {\n          type: 'integer',\n          description: 'Usage limit for this discount, if any.'\n        }\n      },\n      required: [        'amount',\n        'business_id',\n        'code',\n        'created_at',\n        'discount_id',\n        'restricted_to',\n        'times_used',\n        'type'\n      ]\n    },\n    discount_type: {\n      type: 'string',\n      enum: [        'percentage'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      amount: {
        type: 'integer',
        description:
          'The discount amount.\n\n- If `discount_type` is **not** `percentage`, `amount` is in **USD cents**. For example, `100` means `$1.00`.\n  Only USD is allowed.\n- If `discount_type` **is** `percentage`, `amount` is in **basis points**. For example, `540` means `5.4%`.\n\nMust be at least 1.',
      },
      type: {
        $ref: '#/$defs/discount_type',
      },
      code: {
        type: 'string',
        description:
          'Optionally supply a code (will be uppercased).\n- Must be at least 3 characters if provided.\n- If omitted, a random 16-character code is generated.',
      },
      expires_at: {
        type: 'string',
        description: 'When the discount expires, if ever.',
        format: 'date-time',
      },
      name: {
        type: 'string',
      },
      restricted_to: {
        type: 'array',
        description: 'List of product IDs to restrict usage (if any).',
        items: {
          type: 'string',
        },
      },
      subscription_cycles: {
        type: 'integer',
        description:
          'Number of subscription billing cycles this discount is valid for.\nIf not provided, the discount will be applied indefinitely to\nall recurring payments related to the subscription.',
      },
      usage_limit: {
        type: 'integer',
        description: 'How many times this discount can be used (if any).\nMust be >= 1 if provided.',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['amount', 'type'],
    $defs: {
      discount_type: {
        type: 'string',
        enum: ['percentage'],
      },
    },
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.discounts.create(body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/misc/list-supported-countries-misc.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'misc',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/checkout/supported_countries',
  operationId: 'get_supported_countries_proxy',
};

export const tool: Tool = {
  name: 'list_supported_countries_misc',
  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\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/misc_list_supported_countries_response',\n  $defs: {\n    misc_list_supported_countries_response: {\n      type: 'array',\n      items: {\n        $ref: '#/$defs/country_code'\n      }\n    },\n    country_code: {\n      type: 'string',\n      description: 'ISO country code alpha2 variant',\n      enum: [        'AF',\n        'AX',\n        'AL',\n        'DZ',\n        'AS',\n        'AD',\n        'AO',\n        'AI',\n        'AQ',\n        'AG',\n        'AR',\n        'AM',\n        'AW',\n        'AU',\n        'AT',\n        'AZ',\n        'BS',\n        'BH',\n        'BD',\n        'BB',\n        'BY',\n        'BE',\n        'BZ',\n        'BJ',\n        'BM',\n        'BT',\n        'BO',\n        'BQ',\n        'BA',\n        'BW',\n        'BV',\n        'BR',\n        'IO',\n        'BN',\n        'BG',\n        'BF',\n        'BI',\n        'KH',\n        'CM',\n        'CA',\n        'CV',\n        'KY',\n        'CF',\n        'TD',\n        'CL',\n        'CN',\n        'CX',\n        'CC',\n        'CO',\n        'KM',\n        'CG',\n        'CD',\n        'CK',\n        'CR',\n        'CI',\n        'HR',\n        'CU',\n        'CW',\n        'CY',\n        'CZ',\n        'DK',\n        'DJ',\n        'DM',\n        'DO',\n        'EC',\n        'EG',\n        'SV',\n        'GQ',\n        'ER',\n        'EE',\n        'ET',\n        'FK',\n        'FO',\n        'FJ',\n        'FI',\n        'FR',\n        'GF',\n        'PF',\n        'TF',\n        'GA',\n        'GM',\n        'GE',\n        'DE',\n        'GH',\n        'GI',\n        'GR',\n        'GL',\n        'GD',\n        'GP',\n        'GU',\n        'GT',\n        'GG',\n        'GN',\n        'GW',\n        'GY',\n        'HT',\n        'HM',\n        'VA',\n        'HN',\n        'HK',\n        'HU',\n        'IS',\n        'IN',\n        'ID',\n        'IR',\n        'IQ',\n        'IE',\n        'IM',\n        'IL',\n        'IT',\n        'JM',\n        'JP',\n        'JE',\n        'JO',\n        'KZ',\n        'KE',\n        'KI',\n        'KP',\n        'KR',\n        'KW',\n        'KG',\n        'LA',\n        'LV',\n        'LB',\n        'LS',\n        'LR',\n        'LY',\n        'LI',\n        'LT',\n        'LU',\n        'MO',\n        'MK',\n        'MG',\n        'MW',\n        'MY',\n        'MV',\n        'ML',\n        'MT',\n        'MH',\n        'MQ',\n        'MR',\n        'MU',\n        'YT',\n        'MX',\n        'FM',\n        'MD',\n        'MC',\n        'MN',\n        'ME',\n        'MS',\n        'MA',\n        'MZ',\n        'MM',\n        'NA',\n        'NR',\n        'NP',\n        'NL',\n        'NC',\n        'NZ',\n        'NI',\n        'NE',\n        'NG',\n        'NU',\n        'NF',\n        'MP',\n        'NO',\n        'OM',\n        'PK',\n        'PW',\n        'PS',\n        'PA',\n        'PG',\n        'PY',\n        'PE',\n        'PH',\n        'PN',\n        'PL',\n        'PT',\n        'PR',\n        'QA',\n        'RE',\n        'RO',\n        'RU',\n        'RW',\n        'BL',\n        'SH',\n        'KN',\n        'LC',\n        'MF',\n        'PM',\n        'VC',\n        'WS',\n        'SM',\n        'ST',\n        'SA',\n        'SN',\n        'RS',\n        'SC',\n        'SL',\n        'SG',\n        'SX',\n        'SK',\n        'SI',\n        'SB',\n        'SO',\n        'ZA',\n        'GS',\n        'SS',\n        'ES',\n        'LK',\n        'SD',\n        'SR',\n        'SJ',\n        'SZ',\n        'SE',\n        'CH',\n        'SY',\n        'TW',\n        'TJ',\n        'TZ',\n        'TH',\n        'TL',\n        'TG',\n        'TK',\n        'TO',\n        'TT',\n        'TN',\n        'TR',\n        'TM',\n        'TC',\n        'TV',\n        'UG',\n        'UA',\n        'AE',\n        'GB',\n        'UM',\n        'US',\n        'UY',\n        'UZ',\n        'VU',\n        'VE',\n        'VN',\n        'VG',\n        'VI',\n        'WF',\n        'EH',\n        'YE',\n        'ZM',\n        'ZW'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: [],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.misc.listSupportedCountries()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/addons/retrieve-addons.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'addons',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/addons/{id}',
  operationId: 'get_addon_handler',
};

export const tool: Tool = {
  name: 'retrieve_addons',
  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\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/addon_response',\n  $defs: {\n    addon_response: {\n      type: 'object',\n      properties: {\n        id: {\n          type: 'string',\n          description: 'id of the Addon'\n        },\n        business_id: {\n          type: 'string',\n          description: 'Unique identifier for the business to which the addon belongs.'\n        },\n        created_at: {\n          type: 'string',\n          description: 'Created time',\n          format: 'date-time'\n        },\n        currency: {\n          $ref: '#/$defs/currency'\n        },\n        name: {\n          type: 'string',\n          description: 'Name of the Addon'\n        },\n        price: {\n          type: 'integer',\n          description: 'Amount of the addon'\n        },\n        tax_category: {\n          $ref: '#/$defs/tax_category'\n        },\n        updated_at: {\n          type: 'string',\n          description: 'Updated time',\n          format: 'date-time'\n        },\n        description: {\n          type: 'string',\n          description: 'Optional description of the Addon'\n        },\n        image: {\n          type: 'string',\n          description: 'Image of the Addon'\n        }\n      },\n      required: [        'id',\n        'business_id',\n        'created_at',\n        'currency',\n        'name',\n        'price',\n        'tax_category',\n        'updated_at'\n      ]\n    },\n    currency: {\n      type: 'string',\n      enum: [        'AED',\n        'ALL',\n        'AMD',\n        'ANG',\n        'AOA',\n        'ARS',\n        'AUD',\n        'AWG',\n        'AZN',\n        'BAM',\n        'BBD',\n        'BDT',\n        'BGN',\n        'BHD',\n        'BIF',\n        'BMD',\n        'BND',\n        'BOB',\n        'BRL',\n        'BSD',\n        'BWP',\n        'BYN',\n        'BZD',\n        'CAD',\n        'CHF',\n        'CLP',\n        'CNY',\n        'COP',\n        'CRC',\n        'CUP',\n        'CVE',\n        'CZK',\n        'DJF',\n        'DKK',\n        'DOP',\n        'DZD',\n        'EGP',\n        'ETB',\n        'EUR',\n        'FJD',\n        'FKP',\n        'GBP',\n        'GEL',\n        'GHS',\n        'GIP',\n        'GMD',\n        'GNF',\n        'GTQ',\n        'GYD',\n        'HKD',\n        'HNL',\n        'HRK',\n        'HTG',\n        'HUF',\n        'IDR',\n        'ILS',\n        'INR',\n        'IQD',\n        'JMD',\n        'JOD',\n        'JPY',\n        'KES',\n        'KGS',\n        'KHR',\n        'KMF',\n        'KRW',\n        'KWD',\n        'KYD',\n        'KZT',\n        'LAK',\n        'LBP',\n        'LKR',\n        'LRD',\n        'LSL',\n        'LYD',\n        'MAD',\n        'MDL',\n        'MGA',\n        'MKD',\n        'MMK',\n        'MNT',\n        'MOP',\n        'MRU',\n        'MUR',\n        'MVR',\n        'MWK',\n        'MXN',\n        'MYR',\n        'MZN',\n        'NAD',\n        'NGN',\n        'NIO',\n        'NOK',\n        'NPR',\n        'NZD',\n        'OMR',\n        'PAB',\n        'PEN',\n        'PGK',\n        'PHP',\n        'PKR',\n        'PLN',\n        'PYG',\n        'QAR',\n        'RON',\n        'RSD',\n        'RUB',\n        'RWF',\n        'SAR',\n        'SBD',\n        'SCR',\n        'SEK',\n        'SGD',\n        'SHP',\n        'SLE',\n        'SLL',\n        'SOS',\n        'SRD',\n        'SSP',\n        'STN',\n        'SVC',\n        'SZL',\n        'THB',\n        'TND',\n        'TOP',\n        'TRY',\n        'TTD',\n        'TWD',\n        'TZS',\n        'UAH',\n        'UGX',\n        'USD',\n        'UYU',\n        'UZS',\n        'VES',\n        'VND',\n        'VUV',\n        'WST',\n        'XAF',\n        'XCD',\n        'XOF',\n        'XPF',\n        'YER',\n        'ZAR',\n        'ZMW'\n      ]\n    },\n    tax_category: {\n      type: 'string',\n      description: 'Represents the different categories of taxation applicable to various products and services.',\n      enum: [        'digital_products',\n        'saas',\n        'e_book',\n        'edtech'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.addons.retrieve(id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/code-tool.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { dirname } from 'node:path';
import { pathToFileURL } from 'node:url';
import DodoPayments, { ClientOptions } from 'dodopayments';
import { Endpoint, ContentBlock, Metadata } from './tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';

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,
 * 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.
 *
 * @param endpoints - The endpoints to include in the list.
 */
export async function codeTool(): Promise<Endpoint> {
  const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] };
  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.',
    inputSchema: { type: 'object', properties: { code: { type: 'string' } } },
  };

  // Import dynamically to avoid failing at import time in cases where the environment is not well-supported.
  const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker');
  const { workerPath } = await import('./code-tool-paths.cjs');

  const handler = async (client: DodoPayments, args: unknown) => {
    const baseURLHostname = new URL(client.baseURL).hostname;
    const { code } = args as { code: string };

    const worker = await newDenoHTTPWorker(pathToFileURL(workerPath), {
      runFlags: [
        `--node-modules-dir=manual`,
        `--allow-read=code-tool-worker.mjs,${workerPath.replace(/([\/\\]node_modules)[\/\\].+$/, '$1')}/`,
        `--allow-net=${baseURLHostname}`,
        // Allow environment variables because instantiating the client will try to read from them,
        // even though they are not set.
        '--allow-env',
      ],
      printOutput: true,
      spawnOptions: {
        cwd: dirname(workerPath),
      },
    });

    try {
      const resp = await new Promise<Response>((resolve, reject) => {
        worker.addEventListener('exit', (exitCode) => {
          reject(new Error(`Worker exited with code ${exitCode}`));
        });

        const opts: ClientOptions = {
          baseURL: client.baseURL,
          bearerToken: client.bearerToken,
          webhookKey: client.webhookKey,
          defaultHeaders: {
            'X-Stainless-MCP': 'true',
          },
        };

        const req = worker.request(
          'http://localhost',
          {
            headers: {
              'content-type': 'application/json',
            },
            method: 'POST',
          },
          (resp) => {
            const body: Uint8Array[] = [];
            resp.on('error', (err) => {
              reject(err);
            });
            resp.on('data', (chunk) => {
              body.push(chunk);
            });
            resp.on('end', () => {
              resolve(
                new Response(Buffer.concat(body).toString(), {
                  status: resp.statusCode ?? 200,
                  headers: resp.headers as any,
                }),
              );
            });
          },
        );

        const body = JSON.stringify({
          opts,
          code,
        } satisfies WorkerInput);

        req.write(body, (err) => {
          if (err !== null && err !== undefined) {
            reject(err);
          }
        });

        req.end();
      });

      if (resp.status === 200) {
        const { result, logLines, errLines } = (await resp.json()) as WorkerSuccess;
        const returnOutput: ContentBlock | null =
          result === null ? null
          : result === undefined ? null
          : {
              type: 'text',
              text: typeof result === 'string' ? (result as string) : JSON.stringify(result),
            };
        const logOutput: ContentBlock | null =
          logLines.length === 0 ?
            null
          : {
              type: 'text',
              text: logLines.join('\n'),
            };
        const errOutput: ContentBlock | null =
          errLines.length === 0 ?
            null
          : {
              type: 'text',
              text: 'Error output:\n' + errLines.join('\n'),
            };
        return {
          content: [returnOutput, logOutput, errOutput].filter((block) => block !== null),
        };
      } else {
        const { message } = (await resp.json()) as WorkerError;
        throw new Error(message);
      }
    } catch (e) {
      throw e;
    } finally {
      worker.terminate();
    }
  };

  return { metadata, tool, handler };
}

```

--------------------------------------------------------------------------------
/src/resources/discounts.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../core/resource';
import { APIPromise } from '../core/api-promise';
import {
  DefaultPageNumberPagination,
  type DefaultPageNumberPaginationParams,
  PagePromise,
} from '../core/pagination';
import { buildHeaders } from '../internal/headers';
import { RequestOptions } from '../internal/request-options';
import { path } from '../internal/utils/path';

export class Discounts extends APIResource {
  /**
   * POST /discounts If `code` is omitted or empty, a random 16-char uppercase code
   * is generated.
   */
  create(body: DiscountCreateParams, options?: RequestOptions): APIPromise<Discount> {
    return this._client.post('/discounts', { body, ...options });
  }

  /**
   * GET /discounts/{discount_id}
   */
  retrieve(discountID: string, options?: RequestOptions): APIPromise<Discount> {
    return this._client.get(path`/discounts/${discountID}`, options);
  }

  /**
   * PATCH /discounts/{discount_id}
   */
  update(discountID: string, body: DiscountUpdateParams, options?: RequestOptions): APIPromise<Discount> {
    return this._client.patch(path`/discounts/${discountID}`, { body, ...options });
  }

  /**
   * GET /discounts
   */
  list(
    query: DiscountListParams | null | undefined = {},
    options?: RequestOptions,
  ): PagePromise<DiscountsDefaultPageNumberPagination, Discount> {
    return this._client.getAPIList('/discounts', DefaultPageNumberPagination<Discount>, {
      query,
      ...options,
    });
  }

  /**
   * DELETE /discounts/{discount_id}
   */
  delete(discountID: string, options?: RequestOptions): APIPromise<void> {
    return this._client.delete(path`/discounts/${discountID}`, {
      ...options,
      headers: buildHeaders([{ Accept: '*/*' }, options?.headers]),
    });
  }
}

export type DiscountsDefaultPageNumberPagination = DefaultPageNumberPagination<Discount>;

export interface Discount {
  /**
   * The discount amount.
   *
   * - If `discount_type` is `percentage`, this is in **basis points** (e.g., 540 =>
   *   5.4%).
   * - Otherwise, this is **USD cents** (e.g., 100 => `$1.00`).
   */
  amount: number;

  /**
   * The business this discount belongs to.
   */
  business_id: string;

  /**
   * The discount code (up to 16 chars).
   */
  code: string;

  /**
   * Timestamp when the discount is created
   */
  created_at: string;

  /**
   * The unique discount ID
   */
  discount_id: string;

  /**
   * List of product IDs to which this discount is restricted.
   */
  restricted_to: Array<string>;

  /**
   * How many times this discount has been used.
   */
  times_used: number;

  /**
   * The type of discount, e.g. `percentage`, `flat`, or `flat_per_unit`.
   */
  type: DiscountType;

  /**
   * Optional date/time after which discount is expired.
   */
  expires_at?: string | null;

  /**
   * Name for the Discount
   */
  name?: string | null;

  /**
   * Number of subscription billing cycles this discount is valid for. If not
   * provided, the discount will be applied indefinitely to all recurring payments
   * related to the subscription.
   */
  subscription_cycles?: number | null;

  /**
   * Usage limit for this discount, if any.
   */
  usage_limit?: number | null;
}

export type DiscountType = 'percentage';

export interface DiscountCreateParams {
  /**
   * The discount amount.
   *
   * - If `discount_type` is **not** `percentage`, `amount` is in **USD cents**. For
   *   example, `100` means `$1.00`. Only USD is allowed.
   * - If `discount_type` **is** `percentage`, `amount` is in **basis points**. For
   *   example, `540` means `5.4%`.
   *
   * Must be at least 1.
   */
  amount: number;

  /**
   * The discount type (e.g. `percentage`, `flat`, or `flat_per_unit`).
   */
  type: DiscountType;

  /**
   * Optionally supply a code (will be uppercased).
   *
   * - Must be at least 3 characters if provided.
   * - If omitted, a random 16-character code is generated.
   */
  code?: string | null;

  /**
   * When the discount expires, if ever.
   */
  expires_at?: string | null;

  name?: string | null;

  /**
   * List of product IDs to restrict usage (if any).
   */
  restricted_to?: Array<string> | null;

  /**
   * Number of subscription billing cycles this discount is valid for. If not
   * provided, the discount will be applied indefinitely to all recurring payments
   * related to the subscription.
   */
  subscription_cycles?: number | null;

  /**
   * How many times this discount can be used (if any). Must be >= 1 if provided.
   */
  usage_limit?: number | null;
}

export interface DiscountUpdateParams {
  /**
   * If present, update the discount amount:
   *
   * - If `discount_type` is `percentage`, this represents **basis points** (e.g.,
   *   `540` = `5.4%`).
   * - Otherwise, this represents **USD cents** (e.g., `100` = `$1.00`).
   *
   * Must be at least 1 if provided.
   */
  amount?: number | null;

  /**
   * If present, update the discount code (uppercase).
   */
  code?: string | null;

  expires_at?: string | null;

  name?: string | null;

  /**
   * If present, replaces all restricted product IDs with this new set. To remove all
   * restrictions, send empty array
   */
  restricted_to?: Array<string> | null;

  /**
   * Number of subscription billing cycles this discount is valid for. If not
   * provided, the discount will be applied indefinitely to all recurring payments
   * related to the subscription.
   */
  subscription_cycles?: number | null;

  /**
   * If present, update the discount type.
   */
  type?: DiscountType | null;

  usage_limit?: number | null;
}

export interface DiscountListParams extends DefaultPageNumberPaginationParams {}

export declare namespace Discounts {
  export {
    type Discount as Discount,
    type DiscountType as DiscountType,
    type DiscountsDefaultPageNumberPagination as DiscountsDefaultPageNumberPagination,
    type DiscountCreateParams as DiscountCreateParams,
    type DiscountUpdateParams as DiscountUpdateParams,
    type DiscountListParams as DiscountListParams,
  };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/addons/list-addons.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'addons',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/addons',
  operationId: 'list_addons',
};

export const tool: Tool = {
  name: 'list_addons',
  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\n\n\n# Response Schema\n```json\n{\n  type: 'object',\n  properties: {\n    items: {\n      type: 'array',\n      items: {\n        $ref: '#/$defs/addon_response'\n      }\n    }\n  },\n  required: [    'items'\n  ],\n  $defs: {\n    addon_response: {\n      type: 'object',\n      properties: {\n        id: {\n          type: 'string',\n          description: 'id of the Addon'\n        },\n        business_id: {\n          type: 'string',\n          description: 'Unique identifier for the business to which the addon belongs.'\n        },\n        created_at: {\n          type: 'string',\n          description: 'Created time',\n          format: 'date-time'\n        },\n        currency: {\n          $ref: '#/$defs/currency'\n        },\n        name: {\n          type: 'string',\n          description: 'Name of the Addon'\n        },\n        price: {\n          type: 'integer',\n          description: 'Amount of the addon'\n        },\n        tax_category: {\n          $ref: '#/$defs/tax_category'\n        },\n        updated_at: {\n          type: 'string',\n          description: 'Updated time',\n          format: 'date-time'\n        },\n        description: {\n          type: 'string',\n          description: 'Optional description of the Addon'\n        },\n        image: {\n          type: 'string',\n          description: 'Image of the Addon'\n        }\n      },\n      required: [        'id',\n        'business_id',\n        'created_at',\n        'currency',\n        'name',\n        'price',\n        'tax_category',\n        'updated_at'\n      ]\n    },\n    currency: {\n      type: 'string',\n      enum: [        'AED',\n        'ALL',\n        'AMD',\n        'ANG',\n        'AOA',\n        'ARS',\n        'AUD',\n        'AWG',\n        'AZN',\n        'BAM',\n        'BBD',\n        'BDT',\n        'BGN',\n        'BHD',\n        'BIF',\n        'BMD',\n        'BND',\n        'BOB',\n        'BRL',\n        'BSD',\n        'BWP',\n        'BYN',\n        'BZD',\n        'CAD',\n        'CHF',\n        'CLP',\n        'CNY',\n        'COP',\n        'CRC',\n        'CUP',\n        'CVE',\n        'CZK',\n        'DJF',\n        'DKK',\n        'DOP',\n        'DZD',\n        'EGP',\n        'ETB',\n        'EUR',\n        'FJD',\n        'FKP',\n        'GBP',\n        'GEL',\n        'GHS',\n        'GIP',\n        'GMD',\n        'GNF',\n        'GTQ',\n        'GYD',\n        'HKD',\n        'HNL',\n        'HRK',\n        'HTG',\n        'HUF',\n        'IDR',\n        'ILS',\n        'INR',\n        'IQD',\n        'JMD',\n        'JOD',\n        'JPY',\n        'KES',\n        'KGS',\n        'KHR',\n        'KMF',\n        'KRW',\n        'KWD',\n        'KYD',\n        'KZT',\n        'LAK',\n        'LBP',\n        'LKR',\n        'LRD',\n        'LSL',\n        'LYD',\n        'MAD',\n        'MDL',\n        'MGA',\n        'MKD',\n        'MMK',\n        'MNT',\n        'MOP',\n        'MRU',\n        'MUR',\n        'MVR',\n        'MWK',\n        'MXN',\n        'MYR',\n        'MZN',\n        'NAD',\n        'NGN',\n        'NIO',\n        'NOK',\n        'NPR',\n        'NZD',\n        'OMR',\n        'PAB',\n        'PEN',\n        'PGK',\n        'PHP',\n        'PKR',\n        'PLN',\n        'PYG',\n        'QAR',\n        'RON',\n        'RSD',\n        'RUB',\n        'RWF',\n        'SAR',\n        'SBD',\n        'SCR',\n        'SEK',\n        'SGD',\n        'SHP',\n        'SLE',\n        'SLL',\n        'SOS',\n        'SRD',\n        'SSP',\n        'STN',\n        'SVC',\n        'SZL',\n        'THB',\n        'TND',\n        'TOP',\n        'TRY',\n        'TTD',\n        'TWD',\n        'TZS',\n        'UAH',\n        'UGX',\n        'USD',\n        'UYU',\n        'UZS',\n        'VES',\n        'VND',\n        'VUV',\n        'WST',\n        'XAF',\n        'XCD',\n        'XOF',\n        'XPF',\n        'YER',\n        'ZAR',\n        'ZMW'\n      ]\n    },\n    tax_category: {\n      type: 'string',\n      description: 'Represents the different categories of taxation applicable to various products and services.',\n      enum: [        'digital_products',\n        'saas',\n        'e_book',\n        'edtech'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      page_number: {
        type: 'integer',
        description: 'Page number default is 0',
      },
      page_size: {
        type: 'integer',
        description: 'Page size default is 10 max is 100',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: [],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter, ...body } = args as any;
  const response = await client.addons.list(body).asResponse();
  return asTextContentResult(await maybeFilter(jq_filter, await response.json()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/subscriptions/charge-subscriptions.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'subscriptions',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/subscriptions/{subscription_id}/charge',
  operationId: 'create_subscription_charge',
};

export const tool: Tool = {
  name: 'charge_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\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/subscription_charge_response',\n  $defs: {\n    subscription_charge_response: {\n      type: 'object',\n      properties: {\n        payment_id: {\n          type: 'string'\n        }\n      },\n      required: [        'payment_id'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      subscription_id: {
        type: 'string',
      },
      product_price: {
        type: 'integer',
        description:
          'The product price. Represented in the lowest denomination of the currency (e.g., cents for USD).\nFor example, to charge $1.00, pass `100`.',
      },
      adaptive_currency_fees_inclusive: {
        type: 'boolean',
        description:
          'Whether adaptive currency fees should be included in the product_price (true) or added on top (false).\nThis field is ignored if adaptive pricing is not enabled for the business.',
      },
      customer_balance_config: {
        type: 'object',
        description: 'Specify how customer balance is used for the payment',
        properties: {
          allow_customer_credits_purchase: {
            type: 'boolean',
            description: 'Allows Customer Credit to be purchased to settle payments',
          },
          allow_customer_credits_usage: {
            type: 'boolean',
            description: 'Allows Customer Credit Balance to be used to settle payments',
          },
        },
      },
      metadata: {
        type: 'object',
        description:
          'Metadata for the payment. If not passed, the metadata of the subscription will be taken',
        additionalProperties: true,
      },
      product_currency: {
        $ref: '#/$defs/currency',
      },
      product_description: {
        type: 'string',
        description:
          'Optional product description override for billing and line items.\nIf not specified, the stored description of the product will be used.',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['subscription_id', 'product_price'],
    $defs: {
      currency: {
        type: 'string',
        enum: [
          'AED',
          'ALL',
          'AMD',
          'ANG',
          'AOA',
          'ARS',
          'AUD',
          'AWG',
          'AZN',
          'BAM',
          'BBD',
          'BDT',
          'BGN',
          'BHD',
          'BIF',
          'BMD',
          'BND',
          'BOB',
          'BRL',
          'BSD',
          'BWP',
          'BYN',
          'BZD',
          'CAD',
          'CHF',
          'CLP',
          'CNY',
          'COP',
          'CRC',
          'CUP',
          'CVE',
          'CZK',
          'DJF',
          'DKK',
          'DOP',
          'DZD',
          'EGP',
          'ETB',
          'EUR',
          'FJD',
          'FKP',
          'GBP',
          'GEL',
          'GHS',
          'GIP',
          'GMD',
          'GNF',
          'GTQ',
          'GYD',
          'HKD',
          'HNL',
          'HRK',
          'HTG',
          'HUF',
          'IDR',
          'ILS',
          'INR',
          'IQD',
          'JMD',
          'JOD',
          'JPY',
          'KES',
          'KGS',
          'KHR',
          'KMF',
          'KRW',
          'KWD',
          'KYD',
          'KZT',
          'LAK',
          'LBP',
          'LKR',
          'LRD',
          'LSL',
          'LYD',
          'MAD',
          'MDL',
          'MGA',
          'MKD',
          'MMK',
          'MNT',
          'MOP',
          'MRU',
          'MUR',
          'MVR',
          'MWK',
          'MXN',
          'MYR',
          'MZN',
          'NAD',
          'NGN',
          'NIO',
          'NOK',
          'NPR',
          'NZD',
          'OMR',
          'PAB',
          'PEN',
          'PGK',
          'PHP',
          'PKR',
          'PLN',
          'PYG',
          'QAR',
          'RON',
          'RSD',
          'RUB',
          'RWF',
          'SAR',
          'SBD',
          'SCR',
          'SEK',
          'SGD',
          'SHP',
          'SLE',
          'SLL',
          'SOS',
          'SRD',
          'SSP',
          'STN',
          'SVC',
          'SZL',
          'THB',
          'TND',
          'TOP',
          'TRY',
          'TTD',
          'TWD',
          'TZS',
          'UAH',
          'UGX',
          'USD',
          'UYU',
          'UZS',
          'VES',
          'VND',
          'VUV',
          'WST',
          'XAF',
          'XCD',
          'XOF',
          'XPF',
          'YER',
          'ZAR',
          'ZMW',
        ],
      },
    },
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { subscription_id, jq_filter, ...body } = args as any;
  return asTextContentResult(
    await maybeFilter(jq_filter, await client.subscriptions.charge(subscription_id, body)),
  );
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/internal/types.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export type PromiseOrValue<T> = T | Promise<T>;
export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';

export type KeysEnum<T> = { [P in keyof Required<T>]: true };

export type FinalizedRequestInit = RequestInit & { headers: Headers };

type NotAny<T> = [0] extends [1 & T] ? never : T;

/**
 * Some environments overload the global fetch function, and Parameters<T> only gets the last signature.
 */
type OverloadedParameters<T> =
  T extends (
    {
      (...args: infer A): unknown;
      (...args: infer B): unknown;
      (...args: infer C): unknown;
      (...args: infer D): unknown;
    }
  ) ?
    A | B | C | D
  : T extends (
    {
      (...args: infer A): unknown;
      (...args: infer B): unknown;
      (...args: infer C): unknown;
    }
  ) ?
    A | B | C
  : T extends (
    {
      (...args: infer A): unknown;
      (...args: infer B): unknown;
    }
  ) ?
    A | B
  : T extends (...args: infer A) => unknown ? A
  : never;

/* eslint-disable */
/**
 * These imports attempt to get types from a parent package's dependencies.
 * Unresolved bare specifiers can trigger [automatic type acquisition][1] in some projects, which
 * would cause typescript to show types not present at runtime. To avoid this, we import
 * directly from parent node_modules folders.
 *
 * We need to check multiple levels because we don't know what directory structure we'll be in.
 * For example, pnpm generates directories like this:
 * ```
 * node_modules
 * ├── .pnpm
 * │   └── [email protected]
 * │       └── node_modules
 * │           └── pkg
 * │               └── internal
 * │                   └── types.d.ts
 * ├── pkg -> .pnpm/[email protected]/node_modules/pkg
 * └── undici
 * ```
 *
 * [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition
 */
/** @ts-ignore For users with \@types/node */
type UndiciTypesRequestInit = NotAny<import('../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit>;
/** @ts-ignore For users with undici */
type UndiciRequestInit = NotAny<import('../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/undici/index.d.ts').RequestInit>;
/** @ts-ignore For users with \@types/bun */
type BunRequestInit = globalThis.FetchRequestInit;
/** @ts-ignore For users with node-fetch@2 */
type NodeFetch2RequestInit = NotAny<import('../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/@types/node-fetch/index.d.ts').RequestInit>;
/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */
type NodeFetch3RequestInit =  NotAny<import('../node_modules/node-fetch').RequestInit> | NotAny<import('../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../../../node_modules/node-fetch').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/node-fetch').RequestInit>;
/** @ts-ignore For users who use Deno */
type FetchRequestInit = NonNullable<OverloadedParameters<typeof fetch>[1]>;
/* eslint-enable */

type RequestInits =
  | NotAny<UndiciTypesRequestInit>
  | NotAny<UndiciRequestInit>
  | NotAny<BunRequestInit>
  | NotAny<NodeFetch2RequestInit>
  | NotAny<NodeFetch3RequestInit>
  | NotAny<RequestInit>
  | NotAny<FetchRequestInit>;

/**
 * This type contains `RequestInit` options that may be available on the current runtime,
 * including per-platform extensions like `dispatcher`, `agent`, `client`, etc.
 */
export type MergedRequestInit = RequestInits &
  /** We don't include these in the types as they'll be overridden for every request. */
  Partial<Record<'body' | 'headers' | 'method' | 'signal', never>>;

```

--------------------------------------------------------------------------------
/src/internal/detect-platform.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { VERSION } from '../version';

export const isRunningInBrowser = () => {
  return (
    // @ts-ignore
    typeof window !== 'undefined' &&
    // @ts-ignore
    typeof window.document !== 'undefined' &&
    // @ts-ignore
    typeof navigator !== 'undefined'
  );
};

type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown';

/**
 * Note this does not detect 'browser'; for that, use getBrowserInfo().
 */
function getDetectedPlatform(): DetectedPlatform {
  if (typeof Deno !== 'undefined' && Deno.build != null) {
    return 'deno';
  }
  if (typeof EdgeRuntime !== 'undefined') {
    return 'edge';
  }
  if (
    Object.prototype.toString.call(
      typeof (globalThis as any).process !== 'undefined' ? (globalThis as any).process : 0,
    ) === '[object process]'
  ) {
    return 'node';
  }
  return 'unknown';
}

declare const Deno: any;
declare const EdgeRuntime: any;
type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown';
type PlatformName =
  | 'MacOS'
  | 'Linux'
  | 'Windows'
  | 'FreeBSD'
  | 'OpenBSD'
  | 'iOS'
  | 'Android'
  | `Other:${string}`
  | 'Unknown';
type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari';
type PlatformProperties = {
  'X-Stainless-Lang': 'js';
  'X-Stainless-Package-Version': string;
  'X-Stainless-OS': PlatformName;
  'X-Stainless-Arch': Arch;
  'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown';
  'X-Stainless-Runtime-Version': string;
};
const getPlatformProperties = (): PlatformProperties => {
  const detectedPlatform = getDetectedPlatform();
  if (detectedPlatform === 'deno') {
    return {
      'X-Stainless-Lang': 'js',
      'X-Stainless-Package-Version': VERSION,
      'X-Stainless-OS': normalizePlatform(Deno.build.os),
      'X-Stainless-Arch': normalizeArch(Deno.build.arch),
      'X-Stainless-Runtime': 'deno',
      'X-Stainless-Runtime-Version':
        typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown',
    };
  }
  if (typeof EdgeRuntime !== 'undefined') {
    return {
      'X-Stainless-Lang': 'js',
      'X-Stainless-Package-Version': VERSION,
      'X-Stainless-OS': 'Unknown',
      'X-Stainless-Arch': `other:${EdgeRuntime}`,
      'X-Stainless-Runtime': 'edge',
      'X-Stainless-Runtime-Version': (globalThis as any).process.version,
    };
  }
  // Check if Node.js
  if (detectedPlatform === 'node') {
    return {
      'X-Stainless-Lang': 'js',
      'X-Stainless-Package-Version': VERSION,
      'X-Stainless-OS': normalizePlatform((globalThis as any).process.platform ?? 'unknown'),
      'X-Stainless-Arch': normalizeArch((globalThis as any).process.arch ?? 'unknown'),
      'X-Stainless-Runtime': 'node',
      'X-Stainless-Runtime-Version': (globalThis as any).process.version ?? 'unknown',
    };
  }

  const browserInfo = getBrowserInfo();
  if (browserInfo) {
    return {
      'X-Stainless-Lang': 'js',
      'X-Stainless-Package-Version': VERSION,
      'X-Stainless-OS': 'Unknown',
      'X-Stainless-Arch': 'unknown',
      'X-Stainless-Runtime': `browser:${browserInfo.browser}`,
      'X-Stainless-Runtime-Version': browserInfo.version,
    };
  }

  // TODO add support for Cloudflare workers, etc.
  return {
    'X-Stainless-Lang': 'js',
    'X-Stainless-Package-Version': VERSION,
    'X-Stainless-OS': 'Unknown',
    'X-Stainless-Arch': 'unknown',
    'X-Stainless-Runtime': 'unknown',
    'X-Stainless-Runtime-Version': 'unknown',
  };
};

type BrowserInfo = {
  browser: Browser;
  version: string;
};

declare const navigator: { userAgent: string } | undefined;

// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts
function getBrowserInfo(): BrowserInfo | null {
  if (typeof navigator === 'undefined' || !navigator) {
    return null;
  }

  // NOTE: The order matters here!
  const browserPatterns = [
    { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
    { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
    { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ },
    { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
    { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
    { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ },
  ];

  // Find the FIRST matching browser
  for (const { key, pattern } of browserPatterns) {
    const match = pattern.exec(navigator.userAgent);
    if (match) {
      const major = match[1] || 0;
      const minor = match[2] || 0;
      const patch = match[3] || 0;

      return { browser: key, version: `${major}.${minor}.${patch}` };
    }
  }

  return null;
}

const normalizeArch = (arch: string): Arch => {
  // Node docs:
  // - https://nodejs.org/api/process.html#processarch
  // Deno docs:
  // - https://doc.deno.land/deno/stable/~/Deno.build
  if (arch === 'x32') return 'x32';
  if (arch === 'x86_64' || arch === 'x64') return 'x64';
  if (arch === 'arm') return 'arm';
  if (arch === 'aarch64' || arch === 'arm64') return 'arm64';
  if (arch) return `other:${arch}`;
  return 'unknown';
};

const normalizePlatform = (platform: string): PlatformName => {
  // Node platforms:
  // - https://nodejs.org/api/process.html#processplatform
  // Deno platforms:
  // - https://doc.deno.land/deno/stable/~/Deno.build
  // - https://github.com/denoland/deno/issues/14799

  platform = platform.toLowerCase();

  // NOTE: this iOS check is untested and may not work
  // Node does not work natively on IOS, there is a fork at
  // https://github.com/nodejs-mobile/nodejs-mobile
  // however it is unknown at the time of writing how to detect if it is running
  if (platform.includes('ios')) return 'iOS';
  if (platform === 'android') return 'Android';
  if (platform === 'darwin') return 'MacOS';
  if (platform === 'win32') return 'Windows';
  if (platform === 'freebsd') return 'FreeBSD';
  if (platform === 'openbsd') return 'OpenBSD';
  if (platform === 'linux') return 'Linux';
  if (platform) return `Other:${platform}`;
  return 'Unknown';
};

let _platformHeaders: PlatformProperties;
export const getPlatformHeaders = () => {
  return (_platformHeaders ??= getPlatformProperties());
};

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/refunds/retrieve-refunds.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'refunds',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/refunds/{refund_id}',
  operationId: 'get_refund_handler',
};

export const tool: Tool = {
  name: 'retrieve_refunds',
  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\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/refund',\n  $defs: {\n    refund: {\n      type: 'object',\n      properties: {\n        business_id: {\n          type: 'string',\n          description: 'The unique identifier of the business issuing the refund.'\n        },\n        created_at: {\n          type: 'string',\n          description: 'The timestamp of when the refund was created in UTC.',\n          format: 'date-time'\n        },\n        customer: {\n          $ref: '#/$defs/customer_limited_details'\n        },\n        is_partial: {\n          type: 'boolean',\n          description: 'If true the refund is a partial refund'\n        },\n        payment_id: {\n          type: 'string',\n          description: 'The unique identifier of the payment associated with the refund.'\n        },\n        refund_id: {\n          type: 'string',\n          description: 'The unique identifier of the refund.'\n        },\n        status: {\n          $ref: '#/$defs/refund_status'\n        },\n        amount: {\n          type: 'integer',\n          description: 'The refunded amount.'\n        },\n        currency: {\n          $ref: '#/$defs/currency'\n        },\n        reason: {\n          type: 'string',\n          description: 'The reason provided for the refund, if any. Optional.'\n        }\n      },\n      required: [        'business_id',\n        'created_at',\n        'customer',\n        'is_partial',\n        'payment_id',\n        'refund_id',\n        'status'\n      ]\n    },\n    customer_limited_details: {\n      type: 'object',\n      properties: {\n        customer_id: {\n          type: 'string',\n          description: 'Unique identifier for the customer'\n        },\n        email: {\n          type: 'string',\n          description: 'Email address of the customer'\n        },\n        name: {\n          type: 'string',\n          description: 'Full name of the customer'\n        },\n        phone_number: {\n          type: 'string',\n          description: 'Phone number of the customer'\n        }\n      },\n      required: [        'customer_id',\n        'email',\n        'name'\n      ]\n    },\n    refund_status: {\n      type: 'string',\n      enum: [        'succeeded',\n        'failed',\n        'pending',\n        'review'\n      ]\n    },\n    currency: {\n      type: 'string',\n      enum: [        'AED',\n        'ALL',\n        'AMD',\n        'ANG',\n        'AOA',\n        'ARS',\n        'AUD',\n        'AWG',\n        'AZN',\n        'BAM',\n        'BBD',\n        'BDT',\n        'BGN',\n        'BHD',\n        'BIF',\n        'BMD',\n        'BND',\n        'BOB',\n        'BRL',\n        'BSD',\n        'BWP',\n        'BYN',\n        'BZD',\n        'CAD',\n        'CHF',\n        'CLP',\n        'CNY',\n        'COP',\n        'CRC',\n        'CUP',\n        'CVE',\n        'CZK',\n        'DJF',\n        'DKK',\n        'DOP',\n        'DZD',\n        'EGP',\n        'ETB',\n        'EUR',\n        'FJD',\n        'FKP',\n        'GBP',\n        'GEL',\n        'GHS',\n        'GIP',\n        'GMD',\n        'GNF',\n        'GTQ',\n        'GYD',\n        'HKD',\n        'HNL',\n        'HRK',\n        'HTG',\n        'HUF',\n        'IDR',\n        'ILS',\n        'INR',\n        'IQD',\n        'JMD',\n        'JOD',\n        'JPY',\n        'KES',\n        'KGS',\n        'KHR',\n        'KMF',\n        'KRW',\n        'KWD',\n        'KYD',\n        'KZT',\n        'LAK',\n        'LBP',\n        'LKR',\n        'LRD',\n        'LSL',\n        'LYD',\n        'MAD',\n        'MDL',\n        'MGA',\n        'MKD',\n        'MMK',\n        'MNT',\n        'MOP',\n        'MRU',\n        'MUR',\n        'MVR',\n        'MWK',\n        'MXN',\n        'MYR',\n        'MZN',\n        'NAD',\n        'NGN',\n        'NIO',\n        'NOK',\n        'NPR',\n        'NZD',\n        'OMR',\n        'PAB',\n        'PEN',\n        'PGK',\n        'PHP',\n        'PKR',\n        'PLN',\n        'PYG',\n        'QAR',\n        'RON',\n        'RSD',\n        'RUB',\n        'RWF',\n        'SAR',\n        'SBD',\n        'SCR',\n        'SEK',\n        'SGD',\n        'SHP',\n        'SLE',\n        'SLL',\n        'SOS',\n        'SRD',\n        'SSP',\n        'STN',\n        'SVC',\n        'SZL',\n        'THB',\n        'TND',\n        'TOP',\n        'TRY',\n        'TTD',\n        'TWD',\n        'TZS',\n        'UAH',\n        'UGX',\n        'USD',\n        'UYU',\n        'UZS',\n        'VES',\n        'VND',\n        'VUV',\n        'WST',\n        'XAF',\n        'XCD',\n        'XOF',\n        'XPF',\n        'YER',\n        'ZAR',\n        'ZMW'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      refund_id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: ['refund_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { refund_id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.refunds.retrieve(refund_id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/server.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { Endpoint, endpoints, HandlerFunction, query } from './tools';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  SetLevelRequestSchema,
  Implementation,
  Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { ClientOptions } from 'dodopayments';
import DodoPayments from 'dodopayments';
import {
  applyCompatibilityTransformations,
  ClientCapabilities,
  defaultClientCapabilities,
  knownClients,
  parseEmbeddedJSON,
} from './compat';
import { dynamicTools } from './dynamic-tools';
import { codeTool } from './code-tool';
import docsSearchTool from './docs-search-tool';
import { McpOptions } from './options';

export { McpOptions } from './options';
export { ClientType } from './compat';
export { Filter } from './tools';
export { ClientOptions } from 'dodopayments';
export { endpoints } from './tools';

export const newMcpServer = () =>
  new McpServer(
    {
      name: 'dodopayments_api',
      version: '2.4.0',
    },
    { capabilities: { tools: {}, logging: {} } },
  );

// Create server instance
export const server = newMcpServer();

/**
 * Initializes the provided MCP Server with the given tools and handlers.
 * If not provided, the default client, tools and handlers will be used.
 */
export function initMcpServer(params: {
  server: Server | McpServer;
  clientOptions?: ClientOptions;
  mcpOptions?: McpOptions;
}) {
  const server = params.server instanceof McpServer ? params.server.server : params.server;
  const mcpOptions = params.mcpOptions ?? {};

  let providedEndpoints: Endpoint[] | null = null;
  let endpointMap: Record<string, Endpoint> | null = null;

  const initTools = async (implementation?: Implementation) => {
    if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) {
      mcpOptions.client =
        implementation.name.toLowerCase().includes('claude') ? 'claude'
        : implementation.name.toLowerCase().includes('cursor') ? 'cursor'
        : undefined;
      mcpOptions.capabilities = {
        ...(mcpOptions.client && knownClients[mcpOptions.client]),
        ...mcpOptions.capabilities,
      };
    }
    providedEndpoints ??= await selectTools(endpoints, mcpOptions);
    endpointMap ??= Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint]));
  };

  const logAtLevel =
    (level: 'debug' | 'info' | 'warning' | 'error') =>
    (message: string, ...rest: unknown[]) => {
      void server.sendLoggingMessage({
        level,
        data: { message, rest },
      });
    };
  const logger = {
    debug: logAtLevel('debug'),
    info: logAtLevel('info'),
    warn: logAtLevel('warning'),
    error: logAtLevel('error'),
  };

  let client = new DodoPayments({
    ...{ environment: (readEnv('DODO_PAYMENTS_ENVIRONMENT') || undefined) as any },
    logger,
    ...params.clientOptions,
    defaultHeaders: {
      ...params.clientOptions?.defaultHeaders,
      'X-Stainless-MCP': 'true',
    },
  });

  server.setRequestHandler(ListToolsRequestSchema, async () => {
    if (providedEndpoints === null) {
      await initTools(server.getClientVersion());
    }
    return {
      tools: providedEndpoints!.map((endpoint) => endpoint.tool),
    };
  });

  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    if (endpointMap === null) {
      await initTools(server.getClientVersion());
    }
    const { name, arguments: args } = request.params;
    const endpoint = endpointMap![name];
    if (!endpoint) {
      throw new Error(`Unknown tool: ${name}`);
    }

    return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities);
  });

  server.setRequestHandler(SetLevelRequestSchema, async (request) => {
    const { level } = request.params;
    switch (level) {
      case 'debug':
        client = client.withOptions({ logLevel: 'debug' });
        break;
      case 'info':
        client = client.withOptions({ logLevel: 'info' });
        break;
      case 'notice':
      case 'warning':
        client = client.withOptions({ logLevel: 'warn' });
        break;
      case 'error':
        client = client.withOptions({ logLevel: 'error' });
        break;
      default:
        client = client.withOptions({ logLevel: 'off' });
        break;
    }
    return {};
  });
}

/**
 * Selects the tools to include in the MCP Server based on the provided options.
 */
export async function selectTools(endpoints: Endpoint[], options?: McpOptions): Promise<Endpoint[]> {
  const filteredEndpoints = query(options?.filters ?? [], endpoints);

  let includedTools = filteredEndpoints.slice();

  if (includedTools.length > 0) {
    if (options?.includeDynamicTools) {
      includedTools = dynamicTools(includedTools);
    }
  } else {
    if (options?.includeAllTools) {
      includedTools = endpoints.slice();
    } else if (options?.includeDynamicTools) {
      includedTools = dynamicTools(endpoints);
    } else if (options?.includeCodeTools) {
      includedTools = [await codeTool()];
    } else {
      includedTools = endpoints.slice();
    }
  }
  if (options?.includeDocsTools ?? true) {
    includedTools.push(docsSearchTool);
  }
  const capabilities = { ...defaultClientCapabilities, ...options?.capabilities };
  return applyCompatibilityTransformations(includedTools, capabilities);
}

/**
 * Runs the provided handler with the given client and arguments.
 */
export async function executeHandler(
  tool: Tool,
  handler: HandlerFunction,
  client: DodoPayments,
  args: Record<string, unknown> | undefined,
  compatibilityOptions?: Partial<ClientCapabilities>,
) {
  const options = { ...defaultClientCapabilities, ...compatibilityOptions };
  if (!options.validJson && args) {
    args = parseEmbeddedJSON(args, tool.inputSchema);
  }
  return await handler(client, args || {});
}

export const readEnv = (env: string): string | undefined => {
  if (typeof (globalThis as any).process !== 'undefined') {
    return (globalThis as any).process.env?.[env]?.trim();
  } else if (typeof (globalThis as any).Deno !== 'undefined') {
    return (globalThis as any).Deno.env?.get?.(env)?.trim();
  }
  return;
};

export const readEnvOrError = (env: string): string => {
  let envValue = readEnv(env);
  if (envValue === undefined) {
    throw new Error(`Environment variable ${env} is not set`);
  }
  return envValue;
};

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/refunds/list-refunds.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'refunds',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/refunds',
  operationId: 'list_refunds',
};

export const tool: Tool = {
  name: 'list_refunds',
  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\n\n\n# Response Schema\n```json\n{\n  type: 'object',\n  properties: {\n    items: {\n      type: 'array',\n      items: {\n        $ref: '#/$defs/refund_list_response'\n      }\n    }\n  },\n  required: [    'items'\n  ],\n  $defs: {\n    refund_list_response: {\n      type: 'object',\n      properties: {\n        business_id: {\n          type: 'string',\n          description: 'The unique identifier of the business issuing the refund.'\n        },\n        created_at: {\n          type: 'string',\n          description: 'The timestamp of when the refund was created in UTC.',\n          format: 'date-time'\n        },\n        is_partial: {\n          type: 'boolean',\n          description: 'If true the refund is a partial refund'\n        },\n        payment_id: {\n          type: 'string',\n          description: 'The unique identifier of the payment associated with the refund.'\n        },\n        refund_id: {\n          type: 'string',\n          description: 'The unique identifier of the refund.'\n        },\n        status: {\n          $ref: '#/$defs/refund_status'\n        },\n        amount: {\n          type: 'integer',\n          description: 'The refunded amount.'\n        },\n        currency: {\n          $ref: '#/$defs/currency'\n        },\n        reason: {\n          type: 'string',\n          description: 'The reason provided for the refund, if any. Optional.'\n        }\n      },\n      required: [        'business_id',\n        'created_at',\n        'is_partial',\n        'payment_id',\n        'refund_id',\n        'status'\n      ]\n    },\n    refund_status: {\n      type: 'string',\n      enum: [        'succeeded',\n        'failed',\n        'pending',\n        'review'\n      ]\n    },\n    currency: {\n      type: 'string',\n      enum: [        'AED',\n        'ALL',\n        'AMD',\n        'ANG',\n        'AOA',\n        'ARS',\n        'AUD',\n        'AWG',\n        'AZN',\n        'BAM',\n        'BBD',\n        'BDT',\n        'BGN',\n        'BHD',\n        'BIF',\n        'BMD',\n        'BND',\n        'BOB',\n        'BRL',\n        'BSD',\n        'BWP',\n        'BYN',\n        'BZD',\n        'CAD',\n        'CHF',\n        'CLP',\n        'CNY',\n        'COP',\n        'CRC',\n        'CUP',\n        'CVE',\n        'CZK',\n        'DJF',\n        'DKK',\n        'DOP',\n        'DZD',\n        'EGP',\n        'ETB',\n        'EUR',\n        'FJD',\n        'FKP',\n        'GBP',\n        'GEL',\n        'GHS',\n        'GIP',\n        'GMD',\n        'GNF',\n        'GTQ',\n        'GYD',\n        'HKD',\n        'HNL',\n        'HRK',\n        'HTG',\n        'HUF',\n        'IDR',\n        'ILS',\n        'INR',\n        'IQD',\n        'JMD',\n        'JOD',\n        'JPY',\n        'KES',\n        'KGS',\n        'KHR',\n        'KMF',\n        'KRW',\n        'KWD',\n        'KYD',\n        'KZT',\n        'LAK',\n        'LBP',\n        'LKR',\n        'LRD',\n        'LSL',\n        'LYD',\n        'MAD',\n        'MDL',\n        'MGA',\n        'MKD',\n        'MMK',\n        'MNT',\n        'MOP',\n        'MRU',\n        'MUR',\n        'MVR',\n        'MWK',\n        'MXN',\n        'MYR',\n        'MZN',\n        'NAD',\n        'NGN',\n        'NIO',\n        'NOK',\n        'NPR',\n        'NZD',\n        'OMR',\n        'PAB',\n        'PEN',\n        'PGK',\n        'PHP',\n        'PKR',\n        'PLN',\n        'PYG',\n        'QAR',\n        'RON',\n        'RSD',\n        'RUB',\n        'RWF',\n        'SAR',\n        'SBD',\n        'SCR',\n        'SEK',\n        'SGD',\n        'SHP',\n        'SLE',\n        'SLL',\n        'SOS',\n        'SRD',\n        'SSP',\n        'STN',\n        'SVC',\n        'SZL',\n        'THB',\n        'TND',\n        'TOP',\n        'TRY',\n        'TTD',\n        'TWD',\n        'TZS',\n        'UAH',\n        'UGX',\n        'USD',\n        'UYU',\n        'UZS',\n        'VES',\n        'VND',\n        'VUV',\n        'WST',\n        'XAF',\n        'XCD',\n        'XOF',\n        'XPF',\n        'YER',\n        'ZAR',\n        'ZMW'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      created_at_gte: {
        type: 'string',
        description: 'Get events after this created time',
        format: 'date-time',
      },
      created_at_lte: {
        type: 'string',
        description: 'Get events created before this time',
        format: 'date-time',
      },
      customer_id: {
        type: 'string',
        description: 'Filter by customer_id',
      },
      page_number: {
        type: 'integer',
        description: 'Page number default is 0',
      },
      page_size: {
        type: 'integer',
        description: 'Page size default is 10 max is 100',
      },
      status: {
        type: 'string',
        description: 'Filter by status',
        enum: ['succeeded', 'failed', 'pending', 'review'],
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          '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: [],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter, ...body } = args as any;
  const response = await client.refunds.list(body).asResponse();
  return asTextContentResult(await maybeFilter(jq_filter, await response.json()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/tests/tools.test.ts:
--------------------------------------------------------------------------------

```typescript
import { Endpoint, Filter, Metadata, query } from '../src/tools';

describe('Endpoint filtering', () => {
  const endpoints: Endpoint[] = [
    endpoint({
      resource: 'user',
      operation: 'read',
      tags: ['admin'],
      toolName: 'retrieve_user',
    }),
    endpoint({
      resource: 'user.profile',
      operation: 'write',
      tags: [],
      toolName: 'create_user_profile',
    }),
    endpoint({
      resource: 'user.profile',
      operation: 'read',
      tags: [],
      toolName: 'get_user_profile',
    }),
    endpoint({
      resource: 'user.roles.permissions',
      operation: 'write',
      tags: ['admin', 'security'],
      toolName: 'update_user_role_permissions',
    }),
    endpoint({
      resource: 'documents.metadata.tags',
      operation: 'write',
      tags: ['taxonomy', 'metadata'],
      toolName: 'create_document_metadata_tags',
    }),
    endpoint({
      resource: 'organization.settings',
      operation: 'read',
      tags: ['admin', 'configuration'],
      toolName: 'get_organization_settings',
    }),
  ];

  const tests: { name: string; filters: Filter[]; expected: string[] }[] = [
    {
      name: 'match none',
      filters: [],
      expected: [],
    },

    // Resource tests
    {
      name: 'simple resource',
      filters: [{ type: 'resource', op: 'include', value: 'user' }],
      expected: ['retrieve_user'],
    },
    {
      name: 'exclude resource',
      filters: [{ type: 'resource', op: 'exclude', value: 'user' }],
      expected: [
        'create_user_profile',
        'get_user_profile',
        'update_user_role_permissions',
        'create_document_metadata_tags',
        'get_organization_settings',
      ],
    },
    {
      name: 'resource and subresources',
      filters: [{ type: 'resource', op: 'include', value: 'user*' }],
      expected: ['retrieve_user', 'create_user_profile', 'get_user_profile', 'update_user_role_permissions'],
    },
    {
      name: 'just subresources',
      filters: [{ type: 'resource', op: 'include', value: 'user.*' }],
      expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'],
    },
    {
      name: 'specific subresource',
      filters: [{ type: 'resource', op: 'include', value: 'user.roles.permissions' }],
      expected: ['update_user_role_permissions'],
    },
    {
      name: 'deep wildcard match',
      filters: [{ type: 'resource', op: 'include', value: '*.*.tags' }],
      expected: ['create_document_metadata_tags'],
    },

    // Operation tests
    {
      name: 'read operation',
      filters: [{ type: 'operation', op: 'include', value: 'read' }],
      expected: ['retrieve_user', 'get_user_profile', 'get_organization_settings'],
    },
    {
      name: 'write operation',
      filters: [{ type: 'operation', op: 'include', value: 'write' }],
      expected: ['create_user_profile', 'update_user_role_permissions', 'create_document_metadata_tags'],
    },
    {
      name: 'resource and operation combined',
      filters: [
        { type: 'resource', op: 'include', value: 'user.profile' },
        { type: 'operation', op: 'exclude', value: 'write' },
      ],
      expected: ['get_user_profile'],
    },

    // Tag tests
    {
      name: 'admin tag',
      filters: [{ type: 'tag', op: 'include', value: 'admin' }],
      expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'],
    },
    {
      name: 'taxonomy tag',
      filters: [{ type: 'tag', op: 'include', value: 'taxonomy' }],
      expected: ['create_document_metadata_tags'],
    },
    {
      name: 'multiple tags (OR logic)',
      filters: [
        { type: 'tag', op: 'include', value: 'admin' },
        { type: 'tag', op: 'include', value: 'security' },
      ],
      expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'],
    },
    {
      name: 'excluding a tag',
      filters: [
        { type: 'tag', op: 'include', value: 'admin' },
        { type: 'tag', op: 'exclude', value: 'security' },
      ],
      expected: ['retrieve_user', 'get_organization_settings'],
    },

    // Tool name tests
    {
      name: 'tool name match',
      filters: [{ type: 'tool', op: 'include', value: 'get_organization_settings' }],
      expected: ['get_organization_settings'],
    },
    {
      name: 'two tools match',
      filters: [
        { type: 'tool', op: 'include', value: 'get_organization_settings' },
        { type: 'tool', op: 'include', value: 'create_user_profile' },
      ],
      expected: ['create_user_profile', 'get_organization_settings'],
    },
    {
      name: 'excluding tool by name',
      filters: [
        { type: 'resource', op: 'include', value: 'user*' },
        { type: 'tool', op: 'exclude', value: 'retrieve_user' },
      ],
      expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'],
    },

    // Complex combinations
    {
      name: 'complex filter: read operations with admin tag',
      filters: [
        { type: 'operation', op: 'include', value: 'read' },
        { type: 'tag', op: 'include', value: 'admin' },
      ],
      expected: [
        'retrieve_user',
        'get_user_profile',
        'update_user_role_permissions',
        'get_organization_settings',
      ],
    },
    {
      name: 'complex filter: user resources with no tags',
      filters: [
        { type: 'resource', op: 'include', value: 'user.profile' },
        { type: 'tag', op: 'exclude', value: 'admin' },
      ],
      expected: ['create_user_profile', 'get_user_profile'],
    },
    {
      name: 'complex filter: user resources and tags',
      filters: [
        { type: 'resource', op: 'include', value: 'user.profile' },
        { type: 'tag', op: 'include', value: 'admin' },
      ],
      expected: [
        'retrieve_user',
        'create_user_profile',
        'get_user_profile',
        'update_user_role_permissions',
        'get_organization_settings',
      ],
    },
  ];

  tests.forEach((test) => {
    it(`filters by ${test.name}`, () => {
      const filtered = query(test.filters, endpoints);
      expect(filtered.map((e) => e.tool.name)).toEqual(test.expected);
    });
  });
});

function endpoint({
  resource,
  operation,
  tags,
  toolName,
}: {
  resource: string;
  operation: Metadata['operation'];
  tags: string[];
  toolName: string;
}): Endpoint {
  return {
    metadata: {
      resource,
      operation,
      tags,
    },
    tool: { name: toolName, inputSchema: { type: 'object', properties: {} } },
    handler: jest.fn(),
  };
}

```

--------------------------------------------------------------------------------
/src/internal/uploads.ts:
--------------------------------------------------------------------------------

```typescript
import { type RequestOptions } from './request-options';
import type { FilePropertyBag, Fetch } from './builtin-types';
import type { DodoPayments } from '../client';
import { ReadableStreamFrom } from './shims';

export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView;
type FsReadStream = AsyncIterable<Uint8Array> & { path: string | { toString(): string } };

// https://github.com/oven-sh/bun/issues/5980
interface BunFile extends Blob {
  readonly name?: string | undefined;
}

export const checkFileSupport = () => {
  if (typeof File === 'undefined') {
    const { process } = globalThis as any;
    const isOldNode =
      typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20;
    throw new Error(
      '`File` is not defined as a global, which is required for file uploads.' +
        (isOldNode ?
          " Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`."
        : ''),
    );
  }
};

/**
 * Typically, this is a native "File" class.
 *
 * We provide the {@link toFile} utility to convert a variety of objects
 * into the File class.
 *
 * For convenience, you can also pass a fetch Response, or in Node,
 * the result of fs.createReadStream().
 */
export type Uploadable = File | Response | FsReadStream | BunFile;

/**
 * Construct a `File` instance. This is used to ensure a helpful error is thrown
 * for environments that don't define a global `File` yet.
 */
export function makeFile(
  fileBits: BlobPart[],
  fileName: string | undefined,
  options?: FilePropertyBag,
): File {
  checkFileSupport();
  return new File(fileBits as any, fileName ?? 'unknown_file', options);
}

export function getName(value: any): string | undefined {
  return (
    (
      (typeof value === 'object' &&
        value !== null &&
        (('name' in value && value.name && String(value.name)) ||
          ('url' in value && value.url && String(value.url)) ||
          ('filename' in value && value.filename && String(value.filename)) ||
          ('path' in value && value.path && String(value.path)))) ||
      ''
    )
      .split(/[\\/]/)
      .pop() || undefined
  );
}

export const isAsyncIterable = (value: any): value is AsyncIterable<any> =>
  value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function';

/**
 * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value.
 * Otherwise returns the request as is.
 */
export const maybeMultipartFormRequestOptions = async (
  opts: RequestOptions,
  fetch: DodoPayments | Fetch,
): Promise<RequestOptions> => {
  if (!hasUploadableValue(opts.body)) return opts;

  return { ...opts, body: await createForm(opts.body, fetch) };
};

type MultipartFormRequestOptions = Omit<RequestOptions, 'body'> & { body: unknown };

export const multipartFormRequestOptions = async (
  opts: MultipartFormRequestOptions,
  fetch: DodoPayments | Fetch,
): Promise<RequestOptions> => {
  return { ...opts, body: await createForm(opts.body, fetch) };
};

const supportsFormDataMap = /* @__PURE__ */ new WeakMap<Fetch, Promise<boolean>>();

/**
 * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending
 * properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]".
 * This function detects if the fetch function provided supports the global FormData object to avoid
 * confusing error messages later on.
 */
function supportsFormData(fetchObject: DodoPayments | Fetch): Promise<boolean> {
  const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch;
  const cached = supportsFormDataMap.get(fetch);
  if (cached) return cached;
  const promise = (async () => {
    try {
      const FetchResponse = (
        'Response' in fetch ?
          fetch.Response
        : (await fetch('data:,')).constructor) as typeof Response;
      const data = new FormData();
      if (data.toString() === (await new FetchResponse(data).text())) {
        return false;
      }
      return true;
    } catch {
      // avoid false negatives
      return true;
    }
  })();
  supportsFormDataMap.set(fetch, promise);
  return promise;
}

export const createForm = async <T = Record<string, unknown>>(
  body: T | undefined,
  fetch: DodoPayments | Fetch,
): Promise<FormData> => {
  if (!(await supportsFormData(fetch))) {
    throw new TypeError(
      'The provided fetch function does not support file uploads with the current global FormData class.',
    );
  }
  const form = new FormData();
  await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value)));
  return form;
};

// We check for Blob not File because Bun.File doesn't inherit from File,
// but they both inherit from Blob and have a `name` property at runtime.
const isNamedBlob = (value: unknown) => value instanceof Blob && 'name' in value;

const isUploadable = (value: unknown) =>
  typeof value === 'object' &&
  value !== null &&
  (value instanceof Response || isAsyncIterable(value) || isNamedBlob(value));

const hasUploadableValue = (value: unknown): boolean => {
  if (isUploadable(value)) return true;
  if (Array.isArray(value)) return value.some(hasUploadableValue);
  if (value && typeof value === 'object') {
    for (const k in value) {
      if (hasUploadableValue((value as any)[k])) return true;
    }
  }
  return false;
};

const addFormValue = async (form: FormData, key: string, value: unknown): Promise<void> => {
  if (value === undefined) return;
  if (value == null) {
    throw new TypeError(
      `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`,
    );
  }

  // TODO: make nested formats configurable
  if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
    form.append(key, String(value));
  } else if (value instanceof Response) {
    form.append(key, makeFile([await value.blob()], getName(value)));
  } else if (isAsyncIterable(value)) {
    form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value)));
  } else if (isNamedBlob(value)) {
    form.append(key, value, getName(value));
  } else if (Array.isArray(value)) {
    await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry)));
  } else if (typeof value === 'object') {
    await Promise.all(
      Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)),
    );
  } else {
    throw new TypeError(
      `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`,
    );
  }
};

```
Page 3/8FirstPrevNextLast