#
tokens: 48878/50000 52/259 files (page 2/8)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 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
│       │   │   ├── 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

--------------------------------------------------------------------------------
/tests/api-resources/licenses.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 licenses', () => {
  test('activate: only required params', async () => {
    const responsePromise = client.licenses.activate({ license_key: 'license_key', name: '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('activate: required and optional params', async () => {
    const response = await client.licenses.activate({ license_key: 'license_key', name: 'name' });
  });

  test('deactivate: only required params', async () => {
    const responsePromise = client.licenses.deactivate({
      license_key: 'license_key',
      license_key_instance_id: 'license_key_instance_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('deactivate: required and optional params', async () => {
    const response = await client.licenses.deactivate({
      license_key: 'license_key',
      license_key_instance_id: 'license_key_instance_id',
    });
  });

  test('validate: only required params', async () => {
    const responsePromise = client.licenses.validate({ license_key: '2b1f8e2d-c41e-4e8f-b2d3-d9fd61c38f43' });
    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('validate: required and optional params', async () => {
    const response = await client.licenses.validate({
      license_key: '2b1f8e2d-c41e-4e8f-b2d3-d9fd61c38f43',
      license_key_instance_id: 'lki_123',
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/api-resources/refunds.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 refunds', () => {
  test('create: only required params', async () => {
    const responsePromise = client.refunds.create({ payment_id: 'payment_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('create: required and optional params', async () => {
    const response = await client.refunds.create({
      payment_id: 'payment_id',
      items: [{ item_id: 'item_id', amount: 0, tax_inclusive: true }],
      reason: 'reason',
    });
  });

  test('retrieve', async () => {
    const responsePromise = client.refunds.retrieve('refund_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.refunds.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.refunds.list(
        {
          created_at_gte: '2019-12-27T18:11:19.117Z',
          created_at_lte: '2019-12-27T18:11:19.117Z',
          customer_id: 'customer_id',
          page_number: 0,
          page_size: 0,
          status: 'succeeded',
        },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });
});

```

--------------------------------------------------------------------------------
/tests/api-resources/brands.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 brands', () => {
  test('create', async () => {
    const responsePromise = client.brands.create({});
    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('retrieve', async () => {
    const responsePromise = client.brands.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.brands.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.brands.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('updateImages', async () => {
    const responsePromise = client.brands.updateImages('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);
  });
});

```

--------------------------------------------------------------------------------
/src/resources/brands.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 { RequestOptions } from '../internal/request-options';
import { path } from '../internal/utils/path';

export class Brands extends APIResource {
  create(body: BrandCreateParams, options?: RequestOptions): APIPromise<Brand> {
    return this._client.post('/brands', { body, ...options });
  }

  /**
   * Thin handler just calls `get_brand` and wraps in `Json(...)`
   */
  retrieve(id: string, options?: RequestOptions): APIPromise<Brand> {
    return this._client.get(path`/brands/${id}`, options);
  }

  update(id: string, body: BrandUpdateParams, options?: RequestOptions): APIPromise<Brand> {
    return this._client.patch(path`/brands/${id}`, { body, ...options });
  }

  list(options?: RequestOptions): APIPromise<BrandListResponse> {
    return this._client.get('/brands', options);
  }

  updateImages(id: string, options?: RequestOptions): APIPromise<BrandUpdateImagesResponse> {
    return this._client.put(path`/brands/${id}/images`, options);
  }
}

export interface Brand {
  brand_id: string;

  business_id: string;

  enabled: boolean;

  statement_descriptor: string;

  verification_enabled: boolean;

  verification_status: 'Success' | 'Fail' | 'Review' | 'Hold';

  description?: string | null;

  image?: string | null;

  name?: string | null;

  /**
   * Incase the brand verification fails or is put on hold
   */
  reason_for_hold?: string | null;

  support_email?: string | null;

  url?: string | null;
}

export interface BrandListResponse {
  /**
   * List of brands for this business
   */
  items: Array<Brand>;
}

export interface BrandUpdateImagesResponse {
  /**
   * UUID that will be used as the image identifier/key suffix
   */
  image_id: string;

  /**
   * Presigned URL to upload the image
   */
  url: string;
}

export interface BrandCreateParams {
  description?: string | null;

  name?: string | null;

  statement_descriptor?: string | null;

  support_email?: string | null;

  url?: string | null;
}

export interface BrandUpdateParams {
  /**
   * The UUID you got back from the presigned‐upload call
   */
  image_id?: string | null;

  name?: string | null;

  statement_descriptor?: string | null;

  support_email?: string | null;
}

export declare namespace Brands {
  export {
    type Brand as Brand,
    type BrandListResponse as BrandListResponse,
    type BrandUpdateImagesResponse as BrandUpdateImagesResponse,
    type BrandCreateParams as BrandCreateParams,
    type BrandUpdateParams as BrandUpdateParams,
  };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "dodopayments-mcp",
  "version": "2.3.1",
  "description": "The official MCP Server for the Dodo Payments API",
  "author": "Dodo Payments <[email protected]>",
  "types": "dist/index.d.ts",
  "main": "dist/index.js",
  "type": "commonjs",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/dodopayments/dodopayments-typescript.git",
    "directory": "packages/mcp-server"
  },
  "homepage": "https://github.com/dodopayments/dodopayments-typescript/tree/main/packages/mcp-server#readme",
  "license": "Apache-2.0",
  "packageManager": "[email protected]",
  "private": false,
  "publishConfig": {
    "access": "public"
  },
  "scripts": {
    "test": "jest",
    "build": "bash ./build",
    "prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1",
    "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1",
    "format": "prettier --write --cache --cache-strategy metadata . !dist",
    "prepare": "npm run build",
    "tsn": "ts-node -r tsconfig-paths/register",
    "lint": "eslint --ext ts,js .",
    "fix": "eslint --fix --ext ts,js ."
  },
  "dependencies": {
    "dodopayments": "file:../../dist/",
    "@cloudflare/cabidela": "^0.2.4",
    "@modelcontextprotocol/sdk": "^1.11.5",
    "@valtown/deno-http-worker": "^0.0.21",
    "cors": "^2.8.5",
    "express": "^5.1.0",
    "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz",
    "qs": "^6.14.0",
    "yargs": "^17.7.2",
    "zod": "^3.25.20",
    "zod-to-json-schema": "^3.24.5",
    "zod-validation-error": "^4.0.1"
  },
  "bin": {
    "mcp-server": "dist/index.js"
  },
  "devDependencies": {
    "@anthropic-ai/mcpb": "^1.1.0",
    "@types/cors": "^2.8.19",
    "@types/express": "^5.0.3",
    "@types/jest": "^29.4.0",
    "@types/qs": "^6.14.0",
    "@types/yargs": "^17.0.8",
    "@typescript-eslint/eslint-plugin": "8.31.1",
    "@typescript-eslint/parser": "8.31.1",
    "eslint": "^8.49.0",
    "eslint-plugin-prettier": "^5.0.1",
    "eslint-plugin-unused-imports": "^3.0.0",
    "jest": "^29.4.0",
    "prettier": "^3.0.0",
    "ts-jest": "^29.1.0",
    "ts-morph": "^19.0.0",
    "ts-node": "^10.5.0",
    "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz",
    "tsconfig-paths": "^4.0.0",
    "typescript": "5.8.3"
  },
  "imports": {
    "dodopayments-mcp": ".",
    "dodopayments-mcp/*": "./src/*"
  },
  "exports": {
    ".": {
      "require": "./dist/index.js",
      "default": "./dist/index.mjs"
    },
    "./*.mjs": "./dist/*.mjs",
    "./*.js": "./dist/*.js",
    "./*": {
      "require": "./dist/*.js",
      "default": "./dist/*.mjs"
    }
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/cloudflare-worker/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { makeOAuthConsent } from './app';
import { McpAgent } from 'agents/mcp';
import OAuthProvider from '@cloudflare/workers-oauth-provider';
import { McpOptions, initMcpServer, server, ClientOptions } from 'dodopayments-mcp/server';

type MCPProps = {
  clientProps: ClientOptions;
  clientConfig: McpOptions;
};

/**
 * The information displayed on the OAuth consent screen
 */
const serverConfig: ServerConfig = {
  orgName: 'DodoPayments',
  instructionsUrl: undefined, // Set a url for where you show users how to get an API key
  logoUrl: undefined, // Set a custom logo url to appear during the OAuth flow
  clientProperties: [
    {
      key: 'bearerToken',
      label: 'Bearer Token',
      description: 'Bearer Token for API authentication',
      required: true,
      default: undefined,
      placeholder: 'My Bearer Token',
      type: 'password',
    },
    {
      key: 'webhookKey',
      label: 'Webhook Key',
      description: '',
      required: false,
      default: null,
      placeholder: 'My Webhook Key',
      type: 'string',
    },
    {
      key: 'environment',
      label: 'Environment',
      description: 'The environment to use for the client',
      required: false,
      default: 'live_mode',
      placeholder: 'live_mode',
      type: 'select',
      options: [
        { label: 'live_mode', value: 'live_mode' },
        { label: 'test_mode', value: 'test_mode' },
      ],
    },
  ],
};

export class MyMCP extends McpAgent<Env, unknown, MCPProps> {
  server = server;

  async init() {
    initMcpServer({
      server: this.server,
      clientOptions: this.props.clientProps,
      mcpOptions: this.props.clientConfig,
    });
  }
}

export type ServerConfig = {
  /**
   * The name of the company/project
   */
  orgName: string;

  /**
   * An optional company logo image
   */
  logoUrl?: string;

  /**
   * An optional URL with instructions for users to get an API key
   */
  instructionsUrl?: string;

  /**
   * Properties collected to initialize the client
   */
  clientProperties: ClientProperty[];
};

export type ClientProperty = {
  key: string;
  label: string;
  description?: string;
  required: boolean;
  default?: unknown;
  placeholder?: string;
  type: 'string' | 'number' | 'password' | 'select';
  options?: { label: string; value: string }[];
};

// Export the OAuth handler as the default
export default new OAuthProvider({
  apiHandlers: {
    // @ts-expect-error
    '/sse': MyMCP.serveSSE('/sse'), // legacy SSE
    // @ts-expect-error
    '/mcp': MyMCP.serve('/mcp'), // Streaming HTTP
  },
  defaultHandler: makeOAuthConsent(serverConfig),
  authorizeEndpoint: '/authorize',
  tokenEndpoint: '/token',
  clientRegistrationEndpoint: '/register',
});

```

--------------------------------------------------------------------------------
/src/resources/customers/wallets/ledger-entries.ts:
--------------------------------------------------------------------------------

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

import { APIResource } from '../../../core/resource';
import * as MiscAPI from '../../misc';
import * as WalletsAPI from './wallets';
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 LedgerEntries extends APIResource {
  create(
    customerID: string,
    body: LedgerEntryCreateParams,
    options?: RequestOptions,
  ): APIPromise<WalletsAPI.CustomerWallet> {
    return this._client.post(path`/customers/${customerID}/wallets/ledger-entries`, { body, ...options });
  }

  list(
    customerID: string,
    query: LedgerEntryListParams | null | undefined = {},
    options?: RequestOptions,
  ): PagePromise<CustomerWalletTransactionsDefaultPageNumberPagination, CustomerWalletTransaction> {
    return this._client.getAPIList(
      path`/customers/${customerID}/wallets/ledger-entries`,
      DefaultPageNumberPagination<CustomerWalletTransaction>,
      { query, ...options },
    );
  }
}

export type CustomerWalletTransactionsDefaultPageNumberPagination =
  DefaultPageNumberPagination<CustomerWalletTransaction>;

export interface CustomerWalletTransaction {
  id: string;

  after_balance: number;

  amount: number;

  before_balance: number;

  business_id: string;

  created_at: string;

  currency: MiscAPI.Currency;

  customer_id: string;

  event_type:
    | 'payment'
    | 'payment_reversal'
    | 'refund'
    | 'refund_reversal'
    | 'dispute'
    | 'dispute_reversal'
    | 'merchant_adjustment';

  is_credit: boolean;

  reason?: string | null;

  reference_object_id?: string | null;
}

export interface LedgerEntryCreateParams {
  amount: number;

  /**
   * Currency of the wallet to adjust
   */
  currency: MiscAPI.Currency;

  /**
   * Type of ledger entry - credit or debit
   */
  entry_type: 'credit' | 'debit';

  /**
   * Optional idempotency key to prevent duplicate entries
   */
  idempotency_key?: string | null;

  reason?: string | null;
}

export interface LedgerEntryListParams extends DefaultPageNumberPaginationParams {
  /**
   * Optional currency filter
   */
  currency?: MiscAPI.Currency;
}

export declare namespace LedgerEntries {
  export {
    type CustomerWalletTransaction as CustomerWalletTransaction,
    type CustomerWalletTransactionsDefaultPageNumberPagination as CustomerWalletTransactionsDefaultPageNumberPagination,
    type LedgerEntryCreateParams as LedgerEntryCreateParams,
    type LedgerEntryListParams as LedgerEntryListParams,
  };
}

```

--------------------------------------------------------------------------------
/scripts/utils/postprocess-files.cjs:
--------------------------------------------------------------------------------

```
// @ts-check
const fs = require('fs');
const path = require('path');

const distDir =
  process.env['DIST_PATH'] ?
    path.resolve(process.env['DIST_PATH'])
  : path.resolve(__dirname, '..', '..', 'dist');

async function* walk(dir) {
  for await (const d of await fs.promises.opendir(dir)) {
    const entry = path.join(dir, d.name);
    if (d.isDirectory()) yield* walk(entry);
    else if (d.isFile()) yield entry;
  }
}

async function postprocess() {
  for await (const file of walk(distDir)) {
    if (!/(\.d)?[cm]?ts$/.test(file)) continue;

    const code = await fs.promises.readFile(file, 'utf8');

    // strip out lib="dom", types="node", and types="react" references; these
    // are needed at build time, but would pollute the user's TS environment
    const transformed = code.replace(
      /^ *\/\/\/ *<reference +(lib="dom"|types="(node|react)").*?\n/gm,
      // replace with same number of characters to avoid breaking source maps
      (match) => ' '.repeat(match.length - 1) + '\n',
    );

    if (transformed !== code) {
      console.error(`wrote ${path.relative(process.cwd(), file)}`);
      await fs.promises.writeFile(file, transformed, 'utf8');
    }
  }

  const newExports = {
    '.': {
      require: {
        types: './index.d.ts',
        default: './index.js',
      },
      types: './index.d.mts',
      default: './index.mjs',
    },
  };

  for (const entry of await fs.promises.readdir(distDir, { withFileTypes: true })) {
    if (entry.isDirectory() && entry.name !== 'src' && entry.name !== 'internal' && entry.name !== 'bin') {
      const subpath = './' + entry.name;
      newExports[subpath + '/*.mjs'] = {
        default: subpath + '/*.mjs',
      };
      newExports[subpath + '/*.js'] = {
        default: subpath + '/*.js',
      };
      newExports[subpath + '/*'] = {
        import: subpath + '/*.mjs',
        require: subpath + '/*.js',
      };
    } else if (entry.isFile() && /\.[cm]?js$/.test(entry.name)) {
      const { name, ext } = path.parse(entry.name);
      const subpathWithoutExt = './' + name;
      const subpath = './' + entry.name;
      newExports[subpathWithoutExt] ||= { import: undefined, require: undefined };
      const isModule = ext[1] === 'm';
      if (isModule) {
        newExports[subpathWithoutExt].import = subpath;
      } else {
        newExports[subpathWithoutExt].require = subpath;
      }
      newExports[subpath] = {
        default: subpath,
      };
    }
  }
  await fs.promises.writeFile(
    'dist/package.json',
    JSON.stringify(
      Object.assign(
        /** @type {Record<String, unknown>} */ (
          JSON.parse(await fs.promises.readFile('dist/package.json', 'utf-8'))
        ),
        {
          exports: newExports,
        },
      ),
      null,
      2,
    ),
  );
}
postprocess();

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/customers/list-customers.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',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/customers',
  operationId: 'list_customers',
};

export const tool: Tool = {
  name: 'list_customers',
  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/customer'\n      }\n    }\n  },\n  required: [    'items'\n  ],\n  $defs: {\n    customer: {\n      type: 'object',\n      properties: {\n        business_id: {\n          type: 'string'\n        },\n        created_at: {\n          type: 'string',\n          format: 'date-time'\n        },\n        customer_id: {\n          type: 'string'\n        },\n        email: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        },\n        phone_number: {\n          type: 'string'\n        }\n      },\n      required: [        'business_id',\n        'created_at',\n        'customer_id',\n        'email',\n        'name'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      email: {
        type: 'string',
        description: 'Filter by customer email',
      },
      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.customers.list(body).asResponse();
  return asTextContentResult(await maybeFilter(jq_filter, await response.json()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/tests/api-resources/usage-events.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 usageEvents', () => {
  test('retrieve', async () => {
    const responsePromise = client.usageEvents.retrieve('event_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.usageEvents.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.usageEvents.list(
        {
          customer_id: 'customer_id',
          end: '2019-12-27T18:11:19.117Z',
          event_name: 'event_name',
          meter_id: 'meter_id',
          page_number: 0,
          page_size: 0,
          start: '2019-12-27T18:11:19.117Z',
        },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });

  test('ingest: only required params', async () => {
    const responsePromise = client.usageEvents.ingest({
      events: [{ customer_id: 'customer_id', event_id: 'event_id', event_name: 'event_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('ingest: required and optional params', async () => {
    const response = await client.usageEvents.ingest({
      events: [
        {
          customer_id: 'customer_id',
          event_id: 'event_id',
          event_name: 'event_name',
          metadata: { foo: 'string' },
          timestamp: '2019-12-27T18:11:19.117Z',
        },
      ],
    });
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/license-key-instances/list-license-key-instances.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_key_instances',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/license_key_instances',
  operationId: 'list_license_key_instances',
};

export const tool: Tool = {
  name: 'list_license_key_instances',
  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_instance'\n      }\n    }\n  },\n  required: [    'items'\n  ],\n  $defs: {\n    license_key_instance: {\n      type: 'object',\n      properties: {\n        id: {\n          type: 'string'\n        },\n        business_id: {\n          type: 'string'\n        },\n        created_at: {\n          type: 'string',\n          format: 'date-time'\n        },\n        license_key_id: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        }\n      },\n      required: [        'id',\n        'business_id',\n        'created_at',\n        'license_key_id',\n        'name'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      license_key_id: {
        type: 'string',
        description: 'Filter by license key ID',
      },
      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.licenseKeyInstances.list(body).asResponse();
  return asTextContentResult(await maybeFilter(jq_filter, await response.json()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/resources/payouts.ts:
--------------------------------------------------------------------------------

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

import { APIResource } from '../core/resource';
import * as MiscAPI from './misc';
import {
  DefaultPageNumberPagination,
  type DefaultPageNumberPaginationParams,
  PagePromise,
} from '../core/pagination';
import { RequestOptions } from '../internal/request-options';

export class Payouts extends APIResource {
  list(
    query: PayoutListParams | null | undefined = {},
    options?: RequestOptions,
  ): PagePromise<PayoutListResponsesDefaultPageNumberPagination, PayoutListResponse> {
    return this._client.getAPIList('/payouts', DefaultPageNumberPagination<PayoutListResponse>, {
      query,
      ...options,
    });
  }
}

export type PayoutListResponsesDefaultPageNumberPagination = DefaultPageNumberPagination<PayoutListResponse>;

export interface PayoutListResponse {
  /**
   * The total amount of the payout.
   */
  amount: number;

  /**
   * The unique identifier of the business associated with the payout.
   */
  business_id: string;

  /**
   * @deprecated The total value of chargebacks associated with the payout.
   */
  chargebacks: number;

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

  /**
   * The currency of the payout, represented as an ISO 4217 currency code.
   */
  currency: MiscAPI.Currency;

  /**
   * The fee charged for processing the payout.
   */
  fee: number;

  /**
   * The payment method used for the payout (e.g., bank transfer, card, etc.).
   */
  payment_method: string;

  /**
   * The unique identifier of the payout.
   */
  payout_id: string;

  /**
   * @deprecated The total value of refunds associated with the payout.
   */
  refunds: number;

  /**
   * The current status of the payout.
   */
  status: 'not_initiated' | 'in_progress' | 'on_hold' | 'failed' | 'success';

  /**
   * @deprecated The tax applied to the payout.
   */
  tax: number;

  /**
   * The timestamp when the payout was last updated, in UTC.
   */
  updated_at: string;

  /**
   * The name of the payout recipient or purpose.
   */
  name?: string | null;

  /**
   * The URL of the document associated with the payout.
   */
  payout_document_url?: string | null;

  /**
   * Any additional remarks or notes associated with the payout.
   */
  remarks?: string | null;
}

export interface PayoutListParams extends DefaultPageNumberPaginationParams {
  /**
   * Get payouts created after this time (inclusive)
   */
  created_at_gte?: string;

  /**
   * Get payouts created before this time (inclusive)
   */
  created_at_lte?: string;
}

export declare namespace Payouts {
  export {
    type PayoutListResponse as PayoutListResponse,
    type PayoutListResponsesDefaultPageNumberPagination as PayoutListResponsesDefaultPageNumberPagination,
    type PayoutListParams as PayoutListParams,
  };
}

```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
name: CI
on:
  push:
    branches-ignore:
      - 'generated'
      - 'codegen/**'
      - 'integrated/**'
      - 'stl-preview-head/**'
      - 'stl-preview-base/**'
  pull_request:
    branches-ignore:
      - 'stl-preview-head/**'
      - 'stl-preview-base/**'

jobs:
  lint:
    timeout-minutes: 10
    name: lint
    runs-on: ${{ github.repository == 'stainless-sdks/dodo-payments-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
    if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Bootstrap
        run: ./scripts/bootstrap

      - name: Check types
        run: ./scripts/lint

  build:
    timeout-minutes: 5
    name: build
    runs-on: ${{ github.repository == 'stainless-sdks/dodo-payments-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
    if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Bootstrap
        run: ./scripts/bootstrap

      - name: Check build
        run: ./scripts/build

      - name: Get GitHub OIDC Token
        if: github.repository == 'stainless-sdks/dodo-payments-typescript'
        id: github-oidc
        uses: actions/github-script@v6
        with:
          script: core.setOutput('github_token', await core.getIDToken());

      - name: Upload tarball
        if: github.repository == 'stainless-sdks/dodo-payments-typescript'
        env:
          URL: https://pkg.stainless.com/s
          AUTH: ${{ steps.github-oidc.outputs.github_token }}
          SHA: ${{ github.sha }}
        run: ./scripts/utils/upload-artifact.sh

      - name: Upload MCP Server tarball
        if: github.repository == 'stainless-sdks/dodo-payments-typescript'
        env:
          URL: https://pkg.stainless.com/s?subpackage=mcp-server
          AUTH: ${{ steps.github-oidc.outputs.github_token }}
          SHA: ${{ github.sha }}
          BASE_PATH: packages/mcp-server
        run: ./scripts/utils/upload-artifact.sh
  test:
    timeout-minutes: 10
    name: test
    runs-on: ${{ github.repository == 'stainless-sdks/dodo-payments-typescript' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
    if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Bootstrap
        run: ./scripts/bootstrap

      - name: Build
        run: ./scripts/build

      - name: Run tests
        run: ./scripts/test

```

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

```typescript
#!/usr/bin/env node

import { selectTools } from './server';
import { Endpoint, endpoints } from './tools';
import { McpOptions, parseCLIOptions } from './options';
import { launchStdioServer } from './stdio';
import { launchStreamableHTTPServer } from './http';

async function main() {
  const options = parseOptionsOrError();

  if (options.list) {
    listAllTools();
    return;
  }

  const selectedTools = await selectToolsOrError(endpoints, options);

  console.error(
    `MCP Server starting with ${selectedTools.length} tools:`,
    selectedTools.map((e) => e.tool.name),
  );

  switch (options.transport) {
    case 'stdio':
      await launchStdioServer(options);
      break;
    case 'http':
      await launchStreamableHTTPServer(options, options.port ?? options.socket);
      break;
  }
}

if (require.main === module) {
  main().catch((error) => {
    console.error('Fatal error in main():', error);
    process.exit(1);
  });
}

function parseOptionsOrError() {
  try {
    return parseCLIOptions();
  } catch (error) {
    console.error('Error parsing options:', error);
    process.exit(1);
  }
}

async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Promise<Endpoint[]> {
  try {
    const includedTools = await selectTools(endpoints, options);
    if (includedTools.length === 0) {
      console.error('No tools match the provided filters.');
      process.exit(1);
    }
    return includedTools;
  } catch (error) {
    if (error instanceof Error) {
      console.error('Error filtering tools:', error.message);
    } else {
      console.error('Error filtering tools:', error);
    }
    process.exit(1);
  }
}

function listAllTools() {
  if (endpoints.length === 0) {
    console.log('No tools available.');
    return;
  }
  console.log('Available tools:\n');

  // Group endpoints by resource
  const resourceGroups = new Map<string, typeof endpoints>();

  for (const endpoint of endpoints) {
    const resource = endpoint.metadata.resource;
    if (!resourceGroups.has(resource)) {
      resourceGroups.set(resource, []);
    }
    resourceGroups.get(resource)!.push(endpoint);
  }

  // Sort resources alphabetically
  const sortedResources = Array.from(resourceGroups.keys()).sort();

  // Display hierarchically by resource
  for (const resource of sortedResources) {
    console.log(`Resource: ${resource}`);

    const resourceEndpoints = resourceGroups.get(resource)!;
    // Sort endpoints by tool name
    resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name));

    for (const endpoint of resourceEndpoints) {
      const {
        tool,
        metadata: { operation, tags },
      } = endpoint;

      console.log(`  - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`);
      console.log(`    Description: ${tool.description}`);
    }
    console.log('');
  }
}

```

--------------------------------------------------------------------------------
/tests/api-resources/customers/customers.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 customers', () => {
  test('create: only required params', async () => {
    const responsePromise = client.customers.create({ email: 'email', name: '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('create: required and optional params', async () => {
    const response = await client.customers.create({
      email: 'email',
      name: 'name',
      phone_number: 'phone_number',
    });
  });

  test('retrieve', async () => {
    const responsePromise = client.customers.retrieve('customer_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.customers.update('customer_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.customers.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.customers.list(
        { email: 'email', page_number: 0, page_size: 0 },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });
});

```

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

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

export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;

/**
 * An alias to the builtin `RequestInit` type so we can
 * easily alias it in import statements if there are name clashes.
 *
 * https://developer.mozilla.org/docs/Web/API/RequestInit
 */
type _RequestInit = RequestInit;

/**
 * An alias to the builtin `Response` type so we can
 * easily alias it in import statements if there are name clashes.
 *
 * https://developer.mozilla.org/docs/Web/API/Response
 */
type _Response = Response;

/**
 * The type for the first argument to `fetch`.
 *
 * https://developer.mozilla.org/docs/Web/API/Window/fetch#resource
 */
type _RequestInfo = Request | URL | string;

/**
 * The type for constructing `RequestInit` Headers.
 *
 * https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers
 */
type _HeadersInit = RequestInit['headers'];

/**
 * The type for constructing `RequestInit` body.
 *
 * https://developer.mozilla.org/docs/Web/API/RequestInit#body
 */
type _BodyInit = RequestInit['body'];

/**
 * An alias to the builtin `Array<T>` type so we can
 * easily alias it in import statements if there are name clashes.
 */
type _Array<T> = Array<T>;

/**
 * An alias to the builtin `Record<K, T>` type so we can
 * easily alias it in import statements if there are name clashes.
 */
type _Record<K extends keyof any, T> = Record<K, T>;

export type {
  _Array as Array,
  _BodyInit as BodyInit,
  _HeadersInit as HeadersInit,
  _Record as Record,
  _RequestInfo as RequestInfo,
  _RequestInit as RequestInit,
  _Response as Response,
};

/**
 * A copy of the builtin `EndingType` type as it isn't fully supported in certain
 * environments and attempting to reference the global version will error.
 *
 * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941
 */
type EndingType = 'native' | 'transparent';

/**
 * A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain
 * environments and attempting to reference the global version will error.
 *
 * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154
 * https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options
 */
export interface BlobPropertyBag {
  endings?: EndingType;
  type?: string;
}

/**
 * A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain
 * environments and attempting to reference the global version will error.
 *
 * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503
 * https://developer.mozilla.org/en-US/docs/Web/API/File/File#options
 */
export interface FilePropertyBag extends BlobPropertyBag {
  lastModified?: number;
}

```

--------------------------------------------------------------------------------
/src/internal/headers.ts:
--------------------------------------------------------------------------------

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

import { isReadonlyArray } from './utils/values';

type HeaderValue = string | undefined | null;
export type HeadersLike =
  | Headers
  | readonly HeaderValue[][]
  | Record<string, HeaderValue | readonly HeaderValue[]>
  | undefined
  | null
  | NullableHeaders;

const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders');

/**
 * @internal
 * Users can pass explicit nulls to unset default headers. When we parse them
 * into a standard headers type we need to preserve that information.
 */
export type NullableHeaders = {
  /** Brand check, prevent users from creating a NullableHeaders. */
  [brand_privateNullableHeaders]: true;
  /** Parsed headers. */
  values: Headers;
  /** Set of lowercase header names explicitly set to null. */
  nulls: Set<string>;
};

function* iterateHeaders(headers: HeadersLike): IterableIterator<readonly [string, string | null]> {
  if (!headers) return;

  if (brand_privateNullableHeaders in headers) {
    const { values, nulls } = headers;
    yield* values.entries();
    for (const name of nulls) {
      yield [name, null];
    }
    return;
  }

  let shouldClear = false;
  let iter: Iterable<readonly (HeaderValue | readonly HeaderValue[])[]>;
  if (headers instanceof Headers) {
    iter = headers.entries();
  } else if (isReadonlyArray(headers)) {
    iter = headers;
  } else {
    shouldClear = true;
    iter = Object.entries(headers ?? {});
  }
  for (let row of iter) {
    const name = row[0];
    if (typeof name !== 'string') throw new TypeError('expected header name to be a string');
    const values = isReadonlyArray(row[1]) ? row[1] : [row[1]];
    let didClear = false;
    for (const value of values) {
      if (value === undefined) continue;

      // Objects keys always overwrite older headers, they never append.
      // Yield a null to clear the header before adding the new values.
      if (shouldClear && !didClear) {
        didClear = true;
        yield [name, null];
      }
      yield [name, value];
    }
  }
}

export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => {
  const targetHeaders = new Headers();
  const nullHeaders = new Set<string>();
  for (const headers of newHeaders) {
    const seenHeaders = new Set<string>();
    for (const [name, value] of iterateHeaders(headers)) {
      const lowerName = name.toLowerCase();
      if (!seenHeaders.has(lowerName)) {
        targetHeaders.delete(name);
        seenHeaders.add(lowerName);
      }
      if (value === null) {
        targetHeaders.delete(name);
        nullHeaders.add(lowerName);
      } else {
        targetHeaders.append(name, value);
        nullHeaders.delete(lowerName);
      }
    }
  }
  return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders };
};

export const isEmptyHeaders = (headers: HeadersLike) => {
  for (const _ of iterateHeaders(headers)) return false;
  return true;
};

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/brands/retrieve-brands.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: 'brands',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/brands/{id}',
  operationId: 'get_brand_handler',
};

export const tool: Tool = {
  name: 'retrieve_brands',
  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\nThin handler just calls `get_brand` and wraps in `Json(...)`\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/brand',\n  $defs: {\n    brand: {\n      type: 'object',\n      properties: {\n        brand_id: {\n          type: 'string'\n        },\n        business_id: {\n          type: 'string'\n        },\n        enabled: {\n          type: 'boolean'\n        },\n        statement_descriptor: {\n          type: 'string'\n        },\n        verification_enabled: {\n          type: 'boolean'\n        },\n        verification_status: {\n          type: 'string',\n          enum: [            'Success',\n            'Fail',\n            'Review',\n            'Hold'\n          ]\n        },\n        description: {\n          type: 'string'\n        },\n        image: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        },\n        reason_for_hold: {\n          type: 'string',\n          description: 'Incase the brand verification fails or is put on hold'\n        },\n        support_email: {\n          type: 'string'\n        },\n        url: {\n          type: 'string'\n        }\n      },\n      required: [        'brand_id',\n        'business_id',\n        'enabled',\n        'statement_descriptor',\n        'verification_enabled',\n        'verification_status'\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.brands.retrieve(id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/resources/customers/customers.ts:
--------------------------------------------------------------------------------

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

import { APIResource } from '../../core/resource';
import * as CustomerPortalAPI from './customer-portal';
import { CustomerPortal, CustomerPortalCreateParams } from './customer-portal';
import * as WalletsAPI from './wallets/wallets';
import { CustomerWallet, WalletListResponse, Wallets } from './wallets/wallets';
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 Customers extends APIResource {
  customerPortal: CustomerPortalAPI.CustomerPortal = new CustomerPortalAPI.CustomerPortal(this._client);
  wallets: WalletsAPI.Wallets = new WalletsAPI.Wallets(this._client);

  create(body: CustomerCreateParams, options?: RequestOptions): APIPromise<Customer> {
    return this._client.post('/customers', { body, ...options });
  }

  retrieve(customerID: string, options?: RequestOptions): APIPromise<Customer> {
    return this._client.get(path`/customers/${customerID}`, options);
  }

  update(customerID: string, body: CustomerUpdateParams, options?: RequestOptions): APIPromise<Customer> {
    return this._client.patch(path`/customers/${customerID}`, { body, ...options });
  }

  list(
    query: CustomerListParams | null | undefined = {},
    options?: RequestOptions,
  ): PagePromise<CustomersDefaultPageNumberPagination, Customer> {
    return this._client.getAPIList('/customers', DefaultPageNumberPagination<Customer>, {
      query,
      ...options,
    });
  }
}

export type CustomersDefaultPageNumberPagination = DefaultPageNumberPagination<Customer>;

export interface Customer {
  business_id: string;

  created_at: string;

  customer_id: string;

  email: string;

  name: string;

  phone_number?: string | null;
}

export interface CustomerPortalSession {
  link: string;
}

export interface CustomerCreateParams {
  email: string;

  name: string;

  phone_number?: string | null;
}

export interface CustomerUpdateParams {
  name?: string | null;

  phone_number?: string | null;
}

export interface CustomerListParams extends DefaultPageNumberPaginationParams {
  /**
   * Filter by customer email
   */
  email?: string;
}

Customers.CustomerPortal = CustomerPortal;
Customers.Wallets = Wallets;

export declare namespace Customers {
  export {
    type Customer as Customer,
    type CustomerPortalSession as CustomerPortalSession,
    type CustomersDefaultPageNumberPagination as CustomersDefaultPageNumberPagination,
    type CustomerCreateParams as CustomerCreateParams,
    type CustomerUpdateParams as CustomerUpdateParams,
    type CustomerListParams as CustomerListParams,
  };

  export { CustomerPortal as CustomerPortal, type CustomerPortalCreateParams as CustomerPortalCreateParams };

  export {
    Wallets as Wallets,
    type CustomerWallet as CustomerWallet,
    type WalletListResponse as WalletListResponse,
  };
}

```

--------------------------------------------------------------------------------
/src/internal/utils/values.ts:
--------------------------------------------------------------------------------

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

import { DodoPaymentsError } from '../../core/error';

// https://url.spec.whatwg.org/#url-scheme-string
const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;

export const isAbsoluteURL = (url: string): boolean => {
  return startsWithSchemeRegexp.test(url);
};

export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val));
export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[];

/** Returns an object if the given value isn't an object, otherwise returns as-is */
export function maybeObj(x: unknown): object {
  if (typeof x !== 'object') {
    return {};
  }

  return x ?? {};
}

// https://stackoverflow.com/a/34491287
export function isEmptyObj(obj: Object | null | undefined): boolean {
  if (!obj) return true;
  for (const _k in obj) return false;
  return true;
}

// https://eslint.org/docs/latest/rules/no-prototype-builtins
export function hasOwn<T extends object = object>(obj: T, key: PropertyKey): key is keyof T {
  return Object.prototype.hasOwnProperty.call(obj, key);
}

export function isObj(obj: unknown): obj is Record<string, unknown> {
  return obj != null && typeof obj === 'object' && !Array.isArray(obj);
}

export const ensurePresent = <T>(value: T | null | undefined): T => {
  if (value == null) {
    throw new DodoPaymentsError(`Expected a value to be given but received ${value} instead.`);
  }

  return value;
};

export const validatePositiveInteger = (name: string, n: unknown): number => {
  if (typeof n !== 'number' || !Number.isInteger(n)) {
    throw new DodoPaymentsError(`${name} must be an integer`);
  }
  if (n < 0) {
    throw new DodoPaymentsError(`${name} must be a positive integer`);
  }
  return n;
};

export const coerceInteger = (value: unknown): number => {
  if (typeof value === 'number') return Math.round(value);
  if (typeof value === 'string') return parseInt(value, 10);

  throw new DodoPaymentsError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};

export const coerceFloat = (value: unknown): number => {
  if (typeof value === 'number') return value;
  if (typeof value === 'string') return parseFloat(value);

  throw new DodoPaymentsError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};

export const coerceBoolean = (value: unknown): boolean => {
  if (typeof value === 'boolean') return value;
  if (typeof value === 'string') return value === 'true';
  return Boolean(value);
};

export const maybeCoerceInteger = (value: unknown): number | undefined => {
  if (value == null) {
    return undefined;
  }
  return coerceInteger(value);
};

export const maybeCoerceFloat = (value: unknown): number | undefined => {
  if (value == null) {
    return undefined;
  }
  return coerceFloat(value);
};

export const maybeCoerceBoolean = (value: unknown): boolean | undefined => {
  if (value == null) {
    return undefined;
  }
  return coerceBoolean(value);
};

export const safeJSON = (text: string) => {
  try {
    return JSON.parse(text);
  } catch (err) {
    return undefined;
  }
};

```

--------------------------------------------------------------------------------
/src/core/api-promise.ts:
--------------------------------------------------------------------------------

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

import { type DodoPayments } from '../client';

import { type PromiseOrValue } from '../internal/types';
import { APIResponseProps, defaultParseResponse } from '../internal/parse';

/**
 * A subclass of `Promise` providing additional helper methods
 * for interacting with the SDK.
 */
export class APIPromise<T> extends Promise<T> {
  private parsedPromise: Promise<T> | undefined;
  #client: DodoPayments;

  constructor(
    client: DodoPayments,
    private responsePromise: Promise<APIResponseProps>,
    private parseResponse: (
      client: DodoPayments,
      props: APIResponseProps,
    ) => PromiseOrValue<T> = defaultParseResponse,
  ) {
    super((resolve) => {
      // this is maybe a bit weird but this has to be a no-op to not implicitly
      // parse the response body; instead .then, .catch, .finally are overridden
      // to parse the response
      resolve(null as any);
    });
    this.#client = client;
  }

  _thenUnwrap<U>(transform: (data: T, props: APIResponseProps) => U): APIPromise<U> {
    return new APIPromise(this.#client, this.responsePromise, async (client, props) =>
      transform(await this.parseResponse(client, props), props),
    );
  }

  /**
   * Gets the raw `Response` instance instead of parsing the response
   * data.
   *
   * If you want to parse the response body but still get the `Response`
   * instance, you can use {@link withResponse()}.
   *
   * 👋 Getting the wrong TypeScript type for `Response`?
   * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]`
   * to your `tsconfig.json`.
   */
  asResponse(): Promise<Response> {
    return this.responsePromise.then((p) => p.response);
  }

  /**
   * Gets the parsed response data and the raw `Response` instance.
   *
   * If you just want to get the raw `Response` instance without parsing it,
   * you can use {@link asResponse()}.
   *
   * 👋 Getting the wrong TypeScript type for `Response`?
   * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]`
   * to your `tsconfig.json`.
   */
  async withResponse(): Promise<{ data: T; response: Response }> {
    const [data, response] = await Promise.all([this.parse(), this.asResponse()]);
    return { data, response };
  }

  private parse(): Promise<T> {
    if (!this.parsedPromise) {
      this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(this.#client, data));
    }
    return this.parsedPromise;
  }

  override then<TResult1 = T, TResult2 = never>(
    onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
    onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
  ): Promise<TResult1 | TResult2> {
    return this.parse().then(onfulfilled, onrejected);
  }

  override catch<TResult = never>(
    onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null,
  ): Promise<T | TResult> {
    return this.parse().catch(onrejected);
  }

  override finally(onfinally?: (() => void) | undefined | null): Promise<T> {
    return this.parse().finally(onfinally);
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/brands/create-brands.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: 'brands',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/brands',
  operationId: 'create_brand_handler',
};

export const tool: Tool = {
  name: 'create_brands',
  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/brand',\n  $defs: {\n    brand: {\n      type: 'object',\n      properties: {\n        brand_id: {\n          type: 'string'\n        },\n        business_id: {\n          type: 'string'\n        },\n        enabled: {\n          type: 'boolean'\n        },\n        statement_descriptor: {\n          type: 'string'\n        },\n        verification_enabled: {\n          type: 'boolean'\n        },\n        verification_status: {\n          type: 'string',\n          enum: [            'Success',\n            'Fail',\n            'Review',\n            'Hold'\n          ]\n        },\n        description: {\n          type: 'string'\n        },\n        image: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        },\n        reason_for_hold: {\n          type: 'string',\n          description: 'Incase the brand verification fails or is put on hold'\n        },\n        support_email: {\n          type: 'string'\n        },\n        url: {\n          type: 'string'\n        }\n      },\n      required: [        'brand_id',\n        'business_id',\n        'enabled',\n        'statement_descriptor',\n        'verification_enabled',\n        'verification_status'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      description: {
        type: 'string',
      },
      name: {
        type: 'string',
      },
      statement_descriptor: {
        type: 'string',
      },
      support_email: {
        type: 'string',
      },
      url: {
        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: [],
  },
  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.brands.create(body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/resources/licenses.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 { buildHeaders } from '../internal/headers';
import { RequestOptions } from '../internal/request-options';

export class Licenses extends APIResource {
  /**
   * @example
   * ```ts
   * const response = await client.licenses.activate({
   *   license_key: 'license_key',
   *   name: 'name',
   * });
   * ```
   */
  activate(body: LicenseActivateParams, options?: RequestOptions): APIPromise<LicenseActivateResponse> {
    return this._client.post('/licenses/activate', { body, ...options });
  }

  /**
   * @example
   * ```ts
   * await client.licenses.deactivate({
   *   license_key: 'license_key',
   *   license_key_instance_id: 'license_key_instance_id',
   * });
   * ```
   */
  deactivate(body: LicenseDeactivateParams, options?: RequestOptions): APIPromise<void> {
    return this._client.post('/licenses/deactivate', {
      body,
      ...options,
      headers: buildHeaders([{ Accept: '*/*' }, options?.headers]),
    });
  }

  /**
   * @example
   * ```ts
   * const response = await client.licenses.validate({
   *   license_key: '2b1f8e2d-c41e-4e8f-b2d3-d9fd61c38f43',
   * });
   * ```
   */
  validate(body: LicenseValidateParams, options?: RequestOptions): APIPromise<LicenseValidateResponse> {
    return this._client.post('/licenses/validate', { body, ...options });
  }
}

export interface LicenseActivateResponse {
  /**
   * License key instance ID
   */
  id: string;

  /**
   * Business ID
   */
  business_id: string;

  /**
   * Creation timestamp
   */
  created_at: string;

  /**
   * Limited customer details associated with the license key.
   */
  customer: PaymentsAPI.CustomerLimitedDetails;

  /**
   * Associated license key ID
   */
  license_key_id: string;

  /**
   * Instance name
   */
  name: string;

  /**
   * Related product info. Present if the license key is tied to a product.
   */
  product: LicenseActivateResponse.Product;
}

export namespace LicenseActivateResponse {
  /**
   * Related product info. Present if the license key is tied to a product.
   */
  export interface Product {
    /**
     * Unique identifier for the product.
     */
    product_id: string;

    /**
     * Name of the product, if set by the merchant.
     */
    name?: string | null;
  }
}

export interface LicenseValidateResponse {
  valid: boolean;
}

export interface LicenseActivateParams {
  license_key: string;

  name: string;
}

export interface LicenseDeactivateParams {
  license_key: string;

  license_key_instance_id: string;
}

export interface LicenseValidateParams {
  license_key: string;

  license_key_instance_id?: string | null;
}

export declare namespace Licenses {
  export {
    type LicenseActivateResponse as LicenseActivateResponse,
    type LicenseValidateResponse as LicenseValidateResponse,
    type LicenseActivateParams as LicenseActivateParams,
    type LicenseDeactivateParams as LicenseDeactivateParams,
    type LicenseValidateParams as LicenseValidateParams,
  };
}

```

--------------------------------------------------------------------------------
/src/internal/utils/path.ts:
--------------------------------------------------------------------------------

```typescript
import { DodoPaymentsError } from '../../core/error';

/**
 * Percent-encode everything that isn't safe to have in a path without encoding safe chars.
 *
 * Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3:
 * > unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
 * > sub-delims  = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
 * > pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"
 */
export function encodeURIPath(str: string) {
  return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
}

const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));

export const createPathTagFunction = (pathEncoder = encodeURIPath) =>
  function path(statics: readonly string[], ...params: readonly unknown[]): string {
    // If there are no params, no processing is needed.
    if (statics.length === 1) return statics[0]!;

    let postPath = false;
    const invalidSegments = [];
    const path = statics.reduce((previousValue, currentValue, index) => {
      if (/[?#]/.test(currentValue)) {
        postPath = true;
      }
      const value = params[index];
      let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
      if (
        index !== params.length &&
        (value == null ||
          (typeof value === 'object' &&
            // handle values from other realms
            value.toString ===
              Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY)
                ?.toString))
      ) {
        encoded = value + '';
        invalidSegments.push({
          start: previousValue.length + currentValue.length,
          length: encoded.length,
          error: `Value of type ${Object.prototype.toString
            .call(value)
            .slice(8, -1)} is not a valid path parameter`,
        });
      }
      return previousValue + currentValue + (index === params.length ? '' : encoded);
    }, '');

    const pathOnly = path.split(/[?#]/, 1)[0]!;
    const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
    let match;

    // Find all invalid segments
    while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
      invalidSegments.push({
        start: match.index,
        length: match[0].length,
        error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
      });
    }

    invalidSegments.sort((a, b) => a.start - b.start);

    if (invalidSegments.length > 0) {
      let lastEnd = 0;
      const underline = invalidSegments.reduce((acc, segment) => {
        const spaces = ' '.repeat(segment.start - lastEnd);
        const arrows = '^'.repeat(segment.length);
        lastEnd = segment.start + segment.length;
        return acc + spaces + arrows;
      }, '');

      throw new DodoPaymentsError(
        `Path parameters result in path with invalid segments:\n${invalidSegments
          .map((e) => e.error)
          .join('\n')}\n${path}\n${underline}`,
      );
    }

    return path;
  };

/**
 * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced.
 */
export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/brands/list-brands.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: 'brands',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/brands',
  operationId: 'list_brands_handler',
};

export const tool: Tool = {
  name: 'list_brands',
  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/brand_list_response',\n  $defs: {\n    brand_list_response: {\n      type: 'object',\n      properties: {\n        items: {\n          type: 'array',\n          description: 'List of brands for this business',\n          items: {\n            $ref: '#/$defs/brand'\n          }\n        }\n      },\n      required: [        'items'\n      ]\n    },\n    brand: {\n      type: 'object',\n      properties: {\n        brand_id: {\n          type: 'string'\n        },\n        business_id: {\n          type: 'string'\n        },\n        enabled: {\n          type: 'boolean'\n        },\n        statement_descriptor: {\n          type: 'string'\n        },\n        verification_enabled: {\n          type: 'boolean'\n        },\n        verification_status: {\n          type: 'string',\n          enum: [            'Success',\n            'Fail',\n            'Review',\n            'Hold'\n          ]\n        },\n        description: {\n          type: 'string'\n        },\n        image: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        },\n        reason_for_hold: {\n          type: 'string',\n          description: 'Incase the brand verification fails or is put on hold'\n        },\n        support_email: {\n          type: 'string'\n        },\n        url: {\n          type: 'string'\n        }\n      },\n      required: [        'brand_id',\n        'business_id',\n        'enabled',\n        'statement_descriptor',\n        'verification_enabled',\n        'verification_status'\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.brands.list()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/brands/update-brands.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: 'brands',
  operation: 'write',
  tags: [],
  httpMethod: 'patch',
  httpPath: '/brands/{id}',
  operationId: 'patch_brand_handler',
};

export const tool: Tool = {
  name: 'update_brands',
  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/brand',\n  $defs: {\n    brand: {\n      type: 'object',\n      properties: {\n        brand_id: {\n          type: 'string'\n        },\n        business_id: {\n          type: 'string'\n        },\n        enabled: {\n          type: 'boolean'\n        },\n        statement_descriptor: {\n          type: 'string'\n        },\n        verification_enabled: {\n          type: 'boolean'\n        },\n        verification_status: {\n          type: 'string',\n          enum: [            'Success',\n            'Fail',\n            'Review',\n            'Hold'\n          ]\n        },\n        description: {\n          type: 'string'\n        },\n        image: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        },\n        reason_for_hold: {\n          type: 'string',\n          description: 'Incase the brand verification fails or is put on hold'\n        },\n        support_email: {\n          type: 'string'\n        },\n        url: {\n          type: 'string'\n        }\n      },\n      required: [        'brand_id',\n        'business_id',\n        'enabled',\n        'statement_descriptor',\n        'verification_enabled',\n        'verification_status'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
      image_id: {
        type: 'string',
        description: 'The UUID you got back from the presigned‐upload call',
      },
      name: {
        type: 'string',
      },
      statement_descriptor: {
        type: 'string',
      },
      support_email: {
        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: {},
};

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.brands.update(id, body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/internal/utils/log.ts:
--------------------------------------------------------------------------------

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

import { hasOwn } from './values';
import { type DodoPayments } from '../../client';
import { RequestOptions } from '../request-options';

type LogFn = (message: string, ...rest: unknown[]) => void;
export type Logger = {
  error: LogFn;
  warn: LogFn;
  info: LogFn;
  debug: LogFn;
};
export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug';

const levelNumbers = {
  off: 0,
  error: 200,
  warn: 300,
  info: 400,
  debug: 500,
};

export const parseLogLevel = (
  maybeLevel: string | undefined,
  sourceName: string,
  client: DodoPayments,
): LogLevel | undefined => {
  if (!maybeLevel) {
    return undefined;
  }
  if (hasOwn(levelNumbers, maybeLevel)) {
    return maybeLevel;
  }
  loggerFor(client).warn(
    `${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(
      Object.keys(levelNumbers),
    )}`,
  );
  return undefined;
};

function noop() {}

function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) {
  if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
    return noop;
  } else {
    // Don't wrap logger functions, we want the stacktrace intact!
    return logger[fnLevel].bind(logger);
  }
}

const noopLogger = {
  error: noop,
  warn: noop,
  info: noop,
  debug: noop,
};

let cachedLoggers = /* @__PURE__ */ new WeakMap<Logger, [LogLevel, Logger]>();

export function loggerFor(client: DodoPayments): Logger {
  const logger = client.logger;
  const logLevel = client.logLevel ?? 'off';
  if (!logger) {
    return noopLogger;
  }

  const cachedLogger = cachedLoggers.get(logger);
  if (cachedLogger && cachedLogger[0] === logLevel) {
    return cachedLogger[1];
  }

  const levelLogger = {
    error: makeLogFn('error', logger, logLevel),
    warn: makeLogFn('warn', logger, logLevel),
    info: makeLogFn('info', logger, logLevel),
    debug: makeLogFn('debug', logger, logLevel),
  };

  cachedLoggers.set(logger, [logLevel, levelLogger]);

  return levelLogger;
}

export const formatRequestDetails = (details: {
  options?: RequestOptions | undefined;
  headers?: Headers | Record<string, string> | undefined;
  retryOfRequestLogID?: string | undefined;
  retryOf?: string | undefined;
  url?: string | undefined;
  status?: number | undefined;
  method?: string | undefined;
  durationMs?: number | undefined;
  message?: unknown;
  body?: unknown;
}) => {
  if (details.options) {
    details.options = { ...details.options };
    delete details.options['headers']; // redundant + leaks internals
  }
  if (details.headers) {
    details.headers = Object.fromEntries(
      (details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(
        ([name, value]) => [
          name,
          (
            name.toLowerCase() === 'authorization' ||
            name.toLowerCase() === 'cookie' ||
            name.toLowerCase() === 'set-cookie'
          ) ?
            '***'
          : value,
        ],
      ),
    );
  }
  if ('retryOfRequestLogID' in details) {
    if (details.retryOfRequestLogID) {
      details.retryOf = details.retryOfRequestLogID;
    }
    delete details.retryOfRequestLogID;
  }
  return details;
};

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/webhooks/retrieve-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: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/webhooks/{webhook_id}',
  operationId: 'get_webhook',
};

export const tool: Tool = {
  name: 'retrieve_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\nGet 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',
      },
      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'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

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.retrieve(webhook_id)));
};

export default { metadata, tool, handler };

```

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

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

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';

import express from 'express';
import { fromError } from 'zod-validation-error/v3';
import { McpOptions, parseQueryOptions } from './options';
import { ClientOptions, initMcpServer, newMcpServer } from './server';
import { parseAuthHeaders } from './headers';

const newServer = ({
  clientOptions,
  mcpOptions: defaultMcpOptions,
  req,
  res,
}: {
  clientOptions: ClientOptions;
  mcpOptions: McpOptions;
  req: express.Request;
  res: express.Response;
}): McpServer | null => {
  const server = newMcpServer();

  let mcpOptions: McpOptions;
  try {
    mcpOptions = parseQueryOptions(defaultMcpOptions, req.query);
  } catch (error) {
    res.status(400).json({
      jsonrpc: '2.0',
      error: {
        code: -32000,
        message: `Invalid request: ${fromError(error)}`,
      },
    });
    return null;
  }

  try {
    const authOptions = parseAuthHeaders(req);
    initMcpServer({
      server: server,
      clientOptions: {
        ...clientOptions,
        ...authOptions,
      },
      mcpOptions,
    });
  } catch {
    res.status(401).json({
      jsonrpc: '2.0',
      error: {
        code: -32000,
        message: 'Unauthorized',
      },
    });
    return null;
  }

  return server;
};

const post =
  (options: { clientOptions: ClientOptions; mcpOptions: McpOptions }) =>
  async (req: express.Request, res: express.Response) => {
    const server = newServer({ ...options, req, res });
    // If we return null, we already set the authorization error.
    if (server === null) return;
    const transport = new StreamableHTTPServerTransport({
      // Stateless server
      sessionIdGenerator: undefined,
    });
    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
  };

const get = async (req: express.Request, res: express.Response) => {
  res.status(405).json({
    jsonrpc: '2.0',
    error: {
      code: -32000,
      message: 'Method not supported',
    },
  });
};

const del = async (req: express.Request, res: express.Response) => {
  res.status(405).json({
    jsonrpc: '2.0',
    error: {
      code: -32000,
      message: 'Method not supported',
    },
  });
};

export const streamableHTTPApp = ({
  clientOptions = {},
  mcpOptions = {},
}: {
  clientOptions?: ClientOptions;
  mcpOptions?: McpOptions;
}): express.Express => {
  const app = express();
  app.set('query parser', 'extended');
  app.use(express.json());

  app.get('/', get);
  app.post('/', post({ clientOptions, mcpOptions }));
  app.delete('/', del);

  return app;
};

export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => {
  const app = streamableHTTPApp({ mcpOptions: options });
  const server = app.listen(port);
  const address = server.address();

  if (typeof address === 'string') {
    console.error(`MCP Server running on streamable HTTP at ${address}`);
  } else if (address !== null) {
    console.error(`MCP Server running on streamable HTTP on port ${address.port}`);
  } else {
    console.error(`MCP Server running on streamable HTTP on port ${port}`);
  }
};

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/usage-events/retrieve-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/{event_id}',
  operationId: 'get_event_by_id',
};

export const tool: Tool = {
  name: 'retrieve_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 detailed information about a single event using its unique event ID. This endpoint is useful for:\n- Debugging specific event ingestion issues\n- Retrieving event details for customer support\n- Validating that events were processed correctly\n- Getting the complete metadata for an event\n\n## Event ID Format:\nThe event ID should be the same value that was provided during event ingestion via the `/events/ingest` endpoint.\nEvent IDs are case-sensitive and must match exactly.\n\n## Response Details:\nThe response includes all event data including:\n- Complete metadata key-value pairs\n- Original timestamp (preserved from ingestion)\n- Customer and business association\n- Event name and processing information\n\n## Example Usage:\n```text\nGET /events/api_call_12345\n```\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/event',\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: {
      event_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: ['event_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

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

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/tests/api-resources/addons.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 addons', () => {
  test('create: only required params', async () => {
    const responsePromise = client.addons.create({
      currency: 'AED',
      name: 'name',
      price: 0,
      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.addons.create({
      currency: 'AED',
      name: 'name',
      price: 0,
      tax_category: 'digital_products',
      description: 'description',
    });
  });

  test('retrieve', async () => {
    const responsePromise = client.addons.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.addons.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.addons.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.addons.list({ page_number: 0, page_size: 0 }, { path: '/_stainless_unknown_path' }),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });

  test('updateImages', async () => {
    const responsePromise = client.addons.updateImages('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);
  });
});

```

--------------------------------------------------------------------------------
/.github/workflows/docker-mcp.yml:
--------------------------------------------------------------------------------

```yaml
name: Build and Push MCP Server Docker Image

# This workflow is triggered when a GitHub release is created.
# It can also be run manually to re-publish to Docker Hub in case it failed for some reason.
# You can run this workflow by navigating to https://www.github.com/dodopayments/dodopayments-typescript/actions/workflows/docker-mcp.yml
on:
  release:
    types: [published]

  workflow_dispatch:

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: dodopayments/mcp

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      # For OIDC token if using Docker Hub provenance
      id-token: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          driver-opts: |
            image=moby/buildkit:latest

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ vars.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Determine Docker tags
        id: tags
        run: |
          # Get tags from our script
          TAGS=$(bash ./bin/docker-tags)

          # Convert to format expected by docker/metadata-action
          DOCKER_TAGS=""
          while IFS= read -r tag; do
            if [ -n "$DOCKER_TAGS" ]; then
              DOCKER_TAGS="${DOCKER_TAGS}\n"
            fi
            DOCKER_TAGS="${DOCKER_TAGS}type=raw,value=${tag}"
          done <<< "$TAGS"

          # Output for docker/metadata-action
          echo "tags<<EOF" >> $GITHUB_OUTPUT
          echo -e "$DOCKER_TAGS" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

          # Save for build summary
          echo "DOCKER_TAG_LIST<<EOF" >> $GITHUB_ENV
          echo "$TAGS" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            ${{ steps.tags.outputs.tags }}
          labels: |
            org.opencontainers.image.title=Dodo Payments MCP Server
            org.opencontainers.image.description=Model Context Protocol server for Dodo Payments API
            org.opencontainers.image.vendor=Dodo Payments

      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./packages/mcp-server/Dockerfile
          platforms: linux/amd64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          annotations: ${{ steps.meta.outputs.annotations }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          provenance: true
          sbom: true

      - name: Generate build summary
        run: |
          echo "## Docker Build Summary" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "**Image:** \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
          echo "$DOCKER_TAG_LIST" | sed 's/^/- `/' | sed 's/$/`/' >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "**Platforms:** linux/amd64" >> $GITHUB_STEP_SUMMARY

```

--------------------------------------------------------------------------------
/tests/api-resources/discounts.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 discounts', () => {
  test('create: only required params', async () => {
    const responsePromise = client.discounts.create({ amount: 0, type: 'percentage' });
    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.discounts.create({
      amount: 0,
      type: 'percentage',
      code: 'code',
      expires_at: '2019-12-27T18:11:19.117Z',
      name: 'name',
      restricted_to: ['string'],
      subscription_cycles: 0,
      usage_limit: 0,
    });
  });

  test('retrieve', async () => {
    const responsePromise = client.discounts.retrieve('discount_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.discounts.update('discount_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.discounts.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.discounts.list({ page_number: 0, page_size: 0 }, { path: '/_stainless_unknown_path' }),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });

  test('delete', async () => {
    const responsePromise = client.discounts.delete('discount_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);
  });
});

```

--------------------------------------------------------------------------------
/tests/uploads.test.ts:
--------------------------------------------------------------------------------

```typescript
import fs from 'fs';
import type { ResponseLike } from 'dodopayments/internal/to-file';
import { toFile } from 'dodopayments/core/uploads';
import { File } from 'node:buffer';

class MyClass {
  name: string = 'foo';
}

function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike {
  return {
    url,
    blob: async () => content || new Blob([]),
  };
}

describe('toFile', () => {
  it('throws a helpful error for mismatched types', async () => {
    await expect(
      // @ts-expect-error intentionally mismatched type
      toFile({ foo: 'string' }),
    ).rejects.toThrowErrorMatchingInlineSnapshot(
      `"Unexpected data type: object; constructor: Object; props: ["foo"]"`,
    );

    await expect(
      // @ts-expect-error intentionally mismatched type
      toFile(new MyClass()),
    ).rejects.toThrowErrorMatchingInlineSnapshot(
      `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`,
    );
  });

  it('disallows string at the type-level', async () => {
    // @ts-expect-error we intentionally do not type support for `string`
    // to help people avoid passing a file path
    const file = await toFile('contents');
    expect(file.text()).resolves.toEqual('contents');
  });

  it('extracts a file name from a Response', async () => {
    const response = mockResponse({ url: 'https://example.com/my/audio.mp3' });
    const file = await toFile(response);
    expect(file.name).toEqual('audio.mp3');
  });

  it('extracts a file name from a File', async () => {
    const input = new File(['foo'], 'input.jsonl');
    const file = await toFile(input);
    expect(file.name).toEqual('input.jsonl');
  });

  it('extracts a file name from a ReadStream', async () => {
    const input = fs.createReadStream('tests/uploads.test.ts');
    const file = await toFile(input);
    expect(file.name).toEqual('uploads.test.ts');
  });

  it('does not copy File objects', async () => {
    const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' });
    const file = await toFile(input);
    expect(file).toBe(input);
    expect(file.name).toEqual('input.jsonl');
    expect(file.type).toBe('jsonl');
  });

  it('is assignable to File and Blob', async () => {
    const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' });
    const result = await toFile(input);
    const file: File = result;
    const blob: Blob = result;
    void file, blob;
  });
});

describe('missing File error message', () => {
  let prevGlobalFile: unknown;
  let prevNodeFile: unknown;
  beforeEach(() => {
    // The file shim captures the global File object when it's first imported.
    // Reset modules before each test so we can test the error thrown when it's undefined.
    jest.resetModules();
    const buffer = require('node:buffer');
    // @ts-ignore
    prevGlobalFile = globalThis.File;
    prevNodeFile = buffer.File;
    // @ts-ignore
    globalThis.File = undefined;
    buffer.File = undefined;
  });
  afterEach(() => {
    // Clean up
    // @ts-ignore
    globalThis.File = prevGlobalFile;
    require('node:buffer').File = prevNodeFile;
    jest.resetModules();
  });

  test('is thrown', async () => {
    const uploads = await import('dodopayments/core/uploads');
    await expect(
      uploads.toFile(mockResponse({ url: 'https://example.com/my/audio.mp3' })),
    ).rejects.toMatchInlineSnapshot(
      `[Error: \`File\` is not defined as a global, which is required for file uploads.]`,
    );
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/cloudflare-worker/src/app.ts:
--------------------------------------------------------------------------------

```typescript
import { Hono } from 'hono';
import {
  layout,
  homeContent,
  parseApproveFormBody,
  renderAuthorizationApprovedContent,
  renderLoggedOutAuthorizeScreen,
  renderAuthorizationRejectedContent,
} from './utils';
import type { OAuthHelpers } from '@cloudflare/workers-oauth-provider';
import { McpOptions } from 'dodopayments-mcp/server';
import { ServerConfig } from '.';

export type Bindings = Env & {
  OAUTH_PROVIDER: OAuthHelpers;
};

export function makeOAuthConsent(config: ServerConfig, defaultOptions?: Partial<McpOptions>) {
  const app = new Hono<{
    Bindings: Bindings;
  }>();

  // Render a reasonable home page just to show the app is up
  app.get('/', async (c) => {
    const content = await homeContent(c.req.raw);
    return c.html(layout(content, 'Home', config));
  });

  // The /authorize page has a form that will POST to /approve
  app.get('/authorize', async (c) => {
    const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);

    const content = await renderLoggedOutAuthorizeScreen(config, oauthReqInfo, defaultOptions);
    return c.html(layout(content, 'Authorization', config));
  });

  // This endpoint is responsible for validating any login information and
  // then completing the authorization request with the OAUTH_PROVIDER
  app.post('/approve', async (c) => {
    const { action, oauthReqInfo, clientProps, clientConfig } = await parseApproveFormBody(
      await c.req.parseBody(),
      config,
    );

    if (action !== 'login_approve') {
      return c.html(
        layout(
          await renderAuthorizationRejectedContent(oauthReqInfo?.redirectUri || ''),
          'Authorization Status',
          config,
        ),
      );
    }

    if (!oauthReqInfo || !clientProps || !clientConfig) {
      return c.html('INVALID LOGIN', 401);
    }

    // We don't have a real user ID, just tokens, so we generate a random one
    // Make this some stable ID if you want to look up the user's grants later.
    const generatedUserId = crypto.randomUUID();

    const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
      request: oauthReqInfo,
      userId: generatedUserId,
      metadata: {},
      scope: oauthReqInfo.scope,
      props: {
        clientProps,
        clientConfig,
      },
    });

    return c.html(
      layout(await renderAuthorizationApprovedContent(redirectTo), 'Authorization Status', config),
    );
  });

  // Render the authorize screen for demoing the OAuth flow (it won't actually log in)
  app.get('/demo', async (c) => {
    const content = await renderLoggedOutAuthorizeScreen(config, {} as any, defaultOptions);
    return c.html(layout(content, 'Authorization', config));
  });

  // Add a resource server .well-known to point clients to the correct auth server
  const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, OPTIONS',
    'Access-Control-Allow-Headers': '*',
    'Access-Control-Max-Age': '86400',
  };
  app.options('/.well-known/oauth-protected-resource', async (c) => {
    Object.entries(corsHeaders).forEach(([key, value]) => c.header(key, value));
    return c.body(null, 204);
  });
  app.get('/.well-known/oauth-protected-resource', async (c) => {
    Object.entries(corsHeaders).forEach(([key, value]) => c.header(key, value));
    const baseURL = new URL('/', c.req.url).toString();
    return c.json({
      resource: baseURL,
      authorization_servers: [baseURL],
    });
  });

  return app;
}

```

--------------------------------------------------------------------------------
/src/internal/shims.ts:
--------------------------------------------------------------------------------

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

/**
 * This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available.
 *
 * These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error
 * messages in cases where an environment isn't fully supported.
 */

import type { Fetch } from './builtin-types';
import type { ReadableStream } from './shim-types';

export function getDefaultFetch(): Fetch {
  if (typeof fetch !== 'undefined') {
    return fetch as any;
  }

  throw new Error(
    '`fetch` is not defined as a global; Either pass `fetch` to the client, `new DodoPayments({ fetch })` or polyfill the global, `globalThis.fetch = fetch`',
  );
}

type ReadableStreamArgs = ConstructorParameters<typeof ReadableStream>;

export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream {
  const ReadableStream = (globalThis as any).ReadableStream;
  if (typeof ReadableStream === 'undefined') {
    // Note: All of the platforms / runtimes we officially support already define
    // `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes.
    throw new Error(
      '`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`',
    );
  }

  return new ReadableStream(...args);
}

export function ReadableStreamFrom<T>(iterable: Iterable<T> | AsyncIterable<T>): ReadableStream<T> {
  let iter: AsyncIterator<T> | Iterator<T> =
    Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();

  return makeReadableStream({
    start() {},
    async pull(controller: any) {
      const { done, value } = await iter.next();
      if (done) {
        controller.close();
      } else {
        controller.enqueue(value);
      }
    },
    async cancel() {
      await iter.return?.();
    },
  });
}

/**
 * Most browsers don't yet have async iterable support for ReadableStream,
 * and Node has a very different way of reading bytes from its "ReadableStream".
 *
 * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
 */
export function ReadableStreamToAsyncIterable<T>(stream: any): AsyncIterableIterator<T> {
  if (stream[Symbol.asyncIterator]) return stream;

  const reader = stream.getReader();
  return {
    async next() {
      try {
        const result = await reader.read();
        if (result?.done) reader.releaseLock(); // release lock when stream becomes closed
        return result;
      } catch (e) {
        reader.releaseLock(); // release lock when stream becomes errored
        throw e;
      }
    },
    async return() {
      const cancelPromise = reader.cancel();
      reader.releaseLock();
      await cancelPromise;
      return { done: true, value: undefined };
    },
    [Symbol.asyncIterator]() {
      return this;
    },
  };
}

/**
 * Cancels a ReadableStream we don't need to consume.
 * See https://undici.nodejs.org/#/?id=garbage-collection
 */
export async function CancelReadableStream(stream: any): Promise<void> {
  if (stream === null || typeof stream !== 'object') return;

  if (stream[Symbol.asyncIterator]) {
    await stream[Symbol.asyncIterator]().return?.();
    return;
  }

  const reader = stream.getReader();
  const cancelPromise = reader.cancel();
  reader.releaseLock();
  await cancelPromise;
}

```

--------------------------------------------------------------------------------
/scripts/publish-packages.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Called from the `create-releases.yml` workflow with the output
 * of the release please action as the first argument.
 *
 * Example JSON input:
 *
 * ```json
 {
    "releases_created": "true",
    "release_created": "true",
    "id": "137967744",
    "name": "sdk: v0.14.5",
    "tag_name": "sdk-v0.14.5",
    "sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e",
    "body": "## 0.14.5 (2024-01-22)\n\n...",
    "html_url": "https://github.com/$org/$repo/releases/tag/sdk-v0.14.5",
    "draft": "false",
    "upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967744/assets{?name,label}",
    "path": ".",
    "version": "0.14.5",
    "major": "0",
    "minor": "14",
    "patch": "5",
    "packages/additional-sdk--release_created": "true",
    "packages/additional-sdk--id": "137967756",
    "packages/additional-sdk--name": "additional-sdk: v0.5.2",
    "packages/additional-sdk--tag_name": "additional-sdk-v0.5.2",
    "packages/additional-sdk--sha": "7cc2ba5c694e76a117f731d4cf0b06f8b8361f2e",
    "packages/additional-sdk--body": "## 0.5.2 (2024-01-22)\n\n...",
    "packages/additional-sdk--html_url": "https://github.com/$org/$repo/releases/tag/additional-sdk-v0.5.2",
    "packages/additional-sdk--draft": "false",
    "packages/additional-sdk--upload_url": "https://uploads.github.com/repos/$org/$repo/releases/137967756/assets{?name,label}",
    "packages/additional-sdk--path": "packages/additional-sdk",
    "packages/additional-sdk--version": "0.5.2",
    "packages/additional-sdk--major": "0",
    "packages/additional-sdk--minor": "5",
    "packages/additional-sdk--patch": "2",
    "paths_released": "[\".\",\"packages/additional-sdk\"]"
  }
  ```
 */

import { execSync } from 'child_process';
import path from 'path';

function main() {
  const data = process.argv[2] ?? process.env['DATA'];
  if (!data) {
    throw new Error(`Usage: publish-packages.ts '{"json": "obj"}'`);
  }

  const rootDir = path.join(__dirname, '..');
  console.log('root dir', rootDir);
  console.log(`publish-packages called with ${data}`);

  const outputs = JSON.parse(data);

  const rawPaths = outputs.paths_released;

  if (!rawPaths) {
    console.error(JSON.stringify(outputs, null, 2));
    throw new Error('Expected outputs to contain a truthy `paths_released` property');
  }
  if (typeof rawPaths !== 'string') {
    console.error(JSON.stringify(outputs, null, 2));
    throw new Error('Expected outputs `paths_released` property to be a JSON string');
  }

  const paths = JSON.parse(rawPaths);
  if (!Array.isArray(paths)) {
    console.error(JSON.stringify(outputs, null, 2));
    throw new Error('Expected outputs `paths_released` property to be an array');
  }
  if (!paths.length) {
    console.error(JSON.stringify(outputs, null, 2));
    throw new Error('Expected outputs `paths_released` property to contain at least one entry');
  }

  const publishScriptPath = path.join(rootDir, 'bin', 'publish-npm');
  console.log('Using publish script at', publishScriptPath);

  console.log('Ensuring root package is built');
  console.log(`$ yarn build`);
  execSync(`yarn build`, { cwd: rootDir, encoding: 'utf8', stdio: 'inherit' });

  for (const relPackagePath of paths) {
    console.log('\n');

    const packagePath = path.join(rootDir, relPackagePath);
    console.log(`Publishing in directory: ${packagePath}`);

    console.log(`$ yarn install`);
    execSync(`yarn install`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' });

    console.log(`$ bash ${publishScriptPath}`);
    execSync(`bash ${publishScriptPath}`, { cwd: packagePath, encoding: 'utf8', stdio: 'inherit' });
  }

  console.log('Finished publishing packages');
}

main();

```

--------------------------------------------------------------------------------
/tests/api-resources/meters.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 meters', () => {
  test('create: only required params', async () => {
    const responsePromise = client.meters.create({
      aggregation: { type: 'count' },
      event_name: 'event_name',
      measurement_unit: 'measurement_unit',
      name: '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('create: required and optional params', async () => {
    const response = await client.meters.create({
      aggregation: { type: 'count', key: 'key' },
      event_name: 'event_name',
      measurement_unit: 'measurement_unit',
      name: 'name',
      description: 'description',
      filter: {
        clauses: [
          { key: 'user_id', operator: 'equals', value: 'user123' },
          { key: 'amount', operator: 'greater_than', value: 100 },
        ],
        conjunction: 'and',
      },
    });
  });

  test('retrieve', async () => {
    const responsePromise = client.meters.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('list', async () => {
    const responsePromise = client.meters.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.meters.list(
        { archived: true, page_number: 0, page_size: 0 },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });

  test('archive', async () => {
    const responsePromise = client.meters.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.meters.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);
  });
});

```

--------------------------------------------------------------------------------
/src/resources/addons.ts:
--------------------------------------------------------------------------------

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

import { APIResource } from '../core/resource';
import * as MiscAPI from './misc';
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 Addons extends APIResource {
  create(body: AddonCreateParams, options?: RequestOptions): APIPromise<AddonResponse> {
    return this._client.post('/addons', { body, ...options });
  }

  retrieve(id: string, options?: RequestOptions): APIPromise<AddonResponse> {
    return this._client.get(path`/addons/${id}`, options);
  }

  update(id: string, body: AddonUpdateParams, options?: RequestOptions): APIPromise<AddonResponse> {
    return this._client.patch(path`/addons/${id}`, { body, ...options });
  }

  list(
    query: AddonListParams | null | undefined = {},
    options?: RequestOptions,
  ): PagePromise<AddonResponsesDefaultPageNumberPagination, AddonResponse> {
    return this._client.getAPIList('/addons', DefaultPageNumberPagination<AddonResponse>, {
      query,
      ...options,
    });
  }

  updateImages(id: string, options?: RequestOptions): APIPromise<AddonUpdateImagesResponse> {
    return this._client.put(path`/addons/${id}/images`, options);
  }
}

export type AddonResponsesDefaultPageNumberPagination = DefaultPageNumberPagination<AddonResponse>;

export interface AddonResponse {
  /**
   * id of the Addon
   */
  id: string;

  /**
   * Unique identifier for the business to which the addon belongs.
   */
  business_id: string;

  /**
   * Created time
   */
  created_at: string;

  /**
   * Currency of the Addon
   */
  currency: MiscAPI.Currency;

  /**
   * Name of the Addon
   */
  name: string;

  /**
   * Amount of the addon
   */
  price: number;

  /**
   * Tax category applied to this Addon
   */
  tax_category: MiscAPI.TaxCategory;

  /**
   * Updated time
   */
  updated_at: string;

  /**
   * Optional description of the Addon
   */
  description?: string | null;

  /**
   * Image of the Addon
   */
  image?: string | null;
}

export interface AddonUpdateImagesResponse {
  image_id: string;

  url: string;
}

export interface AddonCreateParams {
  /**
   * The currency of the Addon
   */
  currency: MiscAPI.Currency;

  /**
   * Name of the Addon
   */
  name: string;

  /**
   * Amount of the addon
   */
  price: number;

  /**
   * Tax category applied to this Addon
   */
  tax_category: MiscAPI.TaxCategory;

  /**
   * Optional description of the Addon
   */
  description?: string | null;
}

export interface AddonUpdateParams {
  /**
   * The currency of the Addon
   */
  currency?: MiscAPI.Currency | null;

  /**
   * Description of the Addon, optional and must be at most 1000 characters.
   */
  description?: string | null;

  /**
   * Addon image id after its uploaded to S3
   */
  image_id?: string | null;

  /**
   * Name of the Addon, optional and must be at most 100 characters.
   */
  name?: string | null;

  /**
   * Amount of the addon
   */
  price?: number | null;

  /**
   * Tax category of the Addon.
   */
  tax_category?: MiscAPI.TaxCategory | null;
}

export interface AddonListParams extends DefaultPageNumberPaginationParams {}

export declare namespace Addons {
  export {
    type AddonResponse as AddonResponse,
    type AddonUpdateImagesResponse as AddonUpdateImagesResponse,
    type AddonResponsesDefaultPageNumberPagination as AddonResponsesDefaultPageNumberPagination,
    type AddonCreateParams as AddonCreateParams,
    type AddonUpdateParams as AddonUpdateParams,
    type AddonListParams as AddonListParams,
  };
}

```

--------------------------------------------------------------------------------
/tests/api-resources/payments.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 payments', () => {
  test('create: only required params', async () => {
    const responsePromise = client.payments.create({
      billing: { city: 'city', country: 'AF', state: 'state', street: 'street', zipcode: 'zipcode' },
      customer: { customer_id: 'customer_id' },
      product_cart: [{ product_id: 'product_id', quantity: 0 }],
    });
    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.payments.create({
      billing: { city: 'city', country: 'AF', state: 'state', street: 'street', zipcode: 'zipcode' },
      customer: { customer_id: 'customer_id' },
      product_cart: [{ product_id: 'product_id', quantity: 0, amount: 0 }],
      allowed_payment_method_types: ['credit'],
      billing_currency: 'AED',
      discount_code: 'discount_code',
      force_3ds: true,
      metadata: { foo: 'string' },
      payment_link: true,
      return_url: 'return_url',
      show_saved_payment_methods: true,
      tax_id: 'tax_id',
    });
  });

  test('retrieve', async () => {
    const responsePromise = client.payments.retrieve('payment_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.payments.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.payments.list(
        {
          brand_id: 'brand_id',
          created_at_gte: '2019-12-27T18:11:19.117Z',
          created_at_lte: '2019-12-27T18:11:19.117Z',
          customer_id: 'customer_id',
          page_number: 0,
          page_size: 0,
          status: 'succeeded',
          subscription_id: 'subscription_id',
        },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });

  test('retrieveLineItems', async () => {
    const responsePromise = client.payments.retrieveLineItems('payment_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);
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/licenses/activate-licenses.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: 'licenses',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/licenses/activate',
  operationId: 'activate_license_key',
};

export const tool: Tool = {
  name: 'activate_licenses',
  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_activate_response',\n  $defs: {\n    license_activate_response: {\n      type: 'object',\n      properties: {\n        id: {\n          type: 'string',\n          description: 'License key instance ID'\n        },\n        business_id: {\n          type: 'string',\n          description: 'Business ID'\n        },\n        created_at: {\n          type: 'string',\n          description: 'Creation timestamp',\n          format: 'date-time'\n        },\n        customer: {\n          $ref: '#/$defs/customer_limited_details'\n        },\n        license_key_id: {\n          type: 'string',\n          description: 'Associated license key ID'\n        },\n        name: {\n          type: 'string',\n          description: 'Instance name'\n        },\n        product: {\n          type: 'object',\n          description: 'Related product info. Present if the license key is tied to a product.',\n          properties: {\n            product_id: {\n              type: 'string',\n              description: 'Unique identifier for the product.'\n            },\n            name: {\n              type: 'string',\n              description: 'Name of the product, if set by the merchant.'\n            }\n          },\n          required: [            'product_id'\n          ]\n        }\n      },\n      required: [        'id',\n        'business_id',\n        'created_at',\n        'customer',\n        'license_key_id',\n        'name',\n        'product'\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  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      license_key: {
        type: 'string',
      },
      name: {
        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: ['license_key', 'name'],
  },
  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.licenses.activate(body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/core/error.ts:
--------------------------------------------------------------------------------

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

import { castToError } from '../internal/errors';

export class DodoPaymentsError extends Error {}

export class APIError<
  TStatus extends number | undefined = number | undefined,
  THeaders extends Headers | undefined = Headers | undefined,
  TError extends Object | undefined = Object | undefined,
> extends DodoPaymentsError {
  /** HTTP status for the response that caused the error */
  readonly status: TStatus;
  /** HTTP headers for the response that caused the error */
  readonly headers: THeaders;
  /** JSON body of the response that caused the error */
  readonly error: TError;

  constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) {
    super(`${APIError.makeMessage(status, error, message)}`);
    this.status = status;
    this.headers = headers;
    this.error = error;
  }

  private static makeMessage(status: number | undefined, error: any, message: string | undefined) {
    const msg =
      error?.message ?
        typeof error.message === 'string' ?
          error.message
        : JSON.stringify(error.message)
      : error ? JSON.stringify(error)
      : message;

    if (status && msg) {
      return `${status} ${msg}`;
    }
    if (status) {
      return `${status} status code (no body)`;
    }
    if (msg) {
      return msg;
    }
    return '(no status code or body)';
  }

  static generate(
    status: number | undefined,
    errorResponse: Object | undefined,
    message: string | undefined,
    headers: Headers | undefined,
  ): APIError {
    if (!status || !headers) {
      return new APIConnectionError({ message, cause: castToError(errorResponse) });
    }

    const error = errorResponse as Record<string, any>;

    if (status === 400) {
      return new BadRequestError(status, error, message, headers);
    }

    if (status === 401) {
      return new AuthenticationError(status, error, message, headers);
    }

    if (status === 403) {
      return new PermissionDeniedError(status, error, message, headers);
    }

    if (status === 404) {
      return new NotFoundError(status, error, message, headers);
    }

    if (status === 409) {
      return new ConflictError(status, error, message, headers);
    }

    if (status === 422) {
      return new UnprocessableEntityError(status, error, message, headers);
    }

    if (status === 429) {
      return new RateLimitError(status, error, message, headers);
    }

    if (status >= 500) {
      return new InternalServerError(status, error, message, headers);
    }

    return new APIError(status, error, message, headers);
  }
}

export class APIUserAbortError extends APIError<undefined, undefined, undefined> {
  constructor({ message }: { message?: string } = {}) {
    super(undefined, undefined, message || 'Request was aborted.', undefined);
  }
}

export class APIConnectionError extends APIError<undefined, undefined, undefined> {
  constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) {
    super(undefined, undefined, message || 'Connection error.', undefined);
    // in some environments the 'cause' property is already declared
    // @ts-ignore
    if (cause) this.cause = cause;
  }
}

export class APIConnectionTimeoutError extends APIConnectionError {
  constructor({ message }: { message?: string } = {}) {
    super({ message: message ?? 'Request timed out.' });
  }
}

export class BadRequestError extends APIError<400, Headers> {}

export class AuthenticationError extends APIError<401, Headers> {}

export class PermissionDeniedError extends APIError<403, Headers> {}

export class NotFoundError extends APIError<404, Headers> {}

export class ConflictError extends APIError<409, Headers> {}

export class UnprocessableEntityError extends APIError<422, Headers> {}

export class RateLimitError extends APIError<429, Headers> {}

export class InternalServerError extends APIError<number, Headers> {}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/webhooks/list-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: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/webhooks',
  operationId: 'list_webhooks',
};

export const tool: Tool = {
  name: 'list_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\nList all webhooks\n\n# Response Schema\n```json\n{\n  type: 'object',\n  properties: {\n    data: {\n      type: 'array',\n      description: 'List of webhoooks',\n      items: {\n        $ref: '#/$defs/webhook_details'\n      }\n    },\n    done: {\n      type: 'boolean',\n      description: 'true if no more values are to be fetched.'\n    },\n    iterator: {\n      type: 'string',\n      description: 'Cursor pointing to the next paginated object'\n    },\n    prev_iterator: {\n      type: 'string',\n      description: 'Cursor pointing to the previous paginated object'\n    }\n  },\n  required: [    'data',\n    'done'\n  ],\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: {
      iterator: {
        type: 'string',
        description: 'The iterator returned from a prior invocation',
      },
      limit: {
        type: 'integer',
        description: 'Limit the number of returned items',
      },
      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.webhooks.list(body).asResponse();
  return asTextContentResult(await maybeFilter(jq_filter, await response.json()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/license-keys/retrieve-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/{id}',
  operationId: 'get_license_key_handler',
};

export const tool: Tool = {
  name: 'retrieve_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',
      },
      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.licenseKeys.retrieve(id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/discounts/retrieve-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: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/discounts/{discount_id}',
  operationId: 'get_discount_handler',
};

export const tool: Tool = {
  name: 'retrieve_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\nGET /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',
      },
      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'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

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.retrieve(discount_id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/resources/license-keys.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 { RequestOptions } from '../internal/request-options';
import { path } from '../internal/utils/path';

export class LicenseKeys extends APIResource {
  /**
   * @example
   * ```ts
   * const licenseKey = await client.licenseKeys.retrieve(
   *   'lic_123',
   * );
   * ```
   */
  retrieve(id: string, options?: RequestOptions): APIPromise<LicenseKey> {
    return this._client.get(path`/license_keys/${id}`, options);
  }

  /**
   * @example
   * ```ts
   * const licenseKey = await client.licenseKeys.update(
   *   'lic_123',
   * );
   * ```
   */
  update(id: string, body: LicenseKeyUpdateParams, options?: RequestOptions): APIPromise<LicenseKey> {
    return this._client.patch(path`/license_keys/${id}`, { body, ...options });
  }

  /**
   * @example
   * ```ts
   * // Automatically fetches more pages as needed.
   * for await (const licenseKey of client.licenseKeys.list()) {
   *   // ...
   * }
   * ```
   */
  list(
    query: LicenseKeyListParams | null | undefined = {},
    options?: RequestOptions,
  ): PagePromise<LicenseKeysDefaultPageNumberPagination, LicenseKey> {
    return this._client.getAPIList('/license_keys', DefaultPageNumberPagination<LicenseKey>, {
      query,
      ...options,
    });
  }
}

export type LicenseKeysDefaultPageNumberPagination = DefaultPageNumberPagination<LicenseKey>;

export interface LicenseKey {
  /**
   * The unique identifier of the license key.
   */
  id: string;

  /**
   * The unique identifier of the business associated with the license key.
   */
  business_id: string;

  /**
   * The timestamp indicating when the license key was created, in UTC.
   */
  created_at: string;

  /**
   * The unique identifier of the customer associated with the license key.
   */
  customer_id: string;

  /**
   * The current number of instances activated for this license key.
   */
  instances_count: number;

  /**
   * The license key string.
   */
  key: string;

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

  /**
   * The unique identifier of the product associated with the license key.
   */
  product_id: string;

  /**
   * The current status of the license key (e.g., active, inactive, expired).
   */
  status: LicenseKeyStatus;

  /**
   * The maximum number of activations allowed for this license key.
   */
  activations_limit?: number | null;

  /**
   * The timestamp indicating when the license key expires, in UTC.
   */
  expires_at?: string | null;

  /**
   * The unique identifier of the subscription associated with the license key, if
   * any.
   */
  subscription_id?: string | null;
}

export type LicenseKeyStatus = 'active' | 'expired' | 'disabled';

export interface LicenseKeyUpdateParams {
  /**
   * The updated activation limit for the license key. Use `null` to remove the
   * limit, or omit this field to leave it unchanged.
   */
  activations_limit?: number | null;

  /**
   * Indicates whether the license key should be disabled. A value of `true` disables
   * the key, while `false` enables it. Omit this field to leave it unchanged.
   */
  disabled?: boolean | null;

  /**
   * The updated expiration timestamp for the license key in UTC. Use `null` to
   * remove the expiration date, or omit this field to leave it unchanged.
   */
  expires_at?: string | null;
}

export interface LicenseKeyListParams extends DefaultPageNumberPaginationParams {
  /**
   * Filter by customer ID
   */
  customer_id?: string;

  /**
   * Filter by product ID
   */
  product_id?: string;

  /**
   * Filter by license key status
   */
  status?: 'active' | 'expired' | 'disabled';
}

export declare namespace LicenseKeys {
  export {
    type LicenseKey as LicenseKey,
    type LicenseKeyStatus as LicenseKeyStatus,
    type LicenseKeysDefaultPageNumberPagination as LicenseKeysDefaultPageNumberPagination,
    type LicenseKeyUpdateParams as LicenseKeyUpdateParams,
    type LicenseKeyListParams as LicenseKeyListParams,
  };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/usage-events/ingest-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: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/events/ingest',
  operationId: 'ingest_events',
};

export const tool: Tool = {
  name: 'ingest_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\nThis endpoint allows you to ingest custom events that can be used for:\n- Usage-based billing and metering\n- Analytics and reporting\n- Customer behavior tracking\n\n## Important Notes:\n- **Duplicate Prevention**:\n  - Duplicate `event_id` values within the same request are rejected (entire request fails)\n  - Subsequent requests with existing `event_id` values are ignored (idempotent behavior)\n- **Rate Limiting**: Maximum 1000 events per request\n- **Time Validation**: Events with timestamps older than 1 hour or more than 5 minutes in the future will be rejected\n- **Metadata Limits**: Maximum 50 key-value pairs per event, keys max 100 chars, values max 500 chars\n\n## Example Usage:\n```json\n{\n  "events": [\n    {\n      "event_id": "api_call_12345",\n      "customer_id": "cus_abc123",\n      "event_name": "api_request",\n      "timestamp": "2024-01-15T10:30:00Z",\n      "metadata": {\n        "endpoint": "/api/v1/users",\n        "method": "GET",\n        "tokens_used": "150"\n      }\n    }\n  ]\n}\n```\n\n# Response Schema\n```json\n{\n  $ref: \'#/$defs/usage_event_ingest_response\',\n  $defs: {\n    usage_event_ingest_response: {\n      type: \'object\',\n      properties: {\n        ingested_count: {\n          type: \'integer\'\n        }\n      },\n      required: [        \'ingested_count\'\n      ]\n    }\n  }\n}\n```',
  inputSchema: {
    type: 'object',
    properties: {
      events: {
        type: 'array',
        description: 'List of events to be pushed',
        items: {
          $ref: '#/$defs/event_input',
        },
      },
      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: ['events'],
    $defs: {
      event_input: {
        type: 'object',
        properties: {
          customer_id: {
            type: 'string',
            description: 'customer_id of the customer whose usage needs to be tracked',
          },
          event_id: {
            type: 'string',
            description:
              'Event Id acts as an idempotency key. Any subsequent requests with the same event_id will be ignored',
          },
          event_name: {
            type: 'string',
            description: 'Name of the event',
          },
          metadata: {
            type: 'object',
            title: 'EventMetadata',
            description:
              'Custom metadata. Only key value pairs are accepted, objects or arrays submitted will be rejected.',
            additionalProperties: true,
          },
          timestamp: {
            type: 'string',
            description:
              'Custom Timestamp. Defaults to current timestamp in UTC.\nTimestamps that are older that 1 hour or after 5 mins, from current timestamp, will be rejected.',
            format: 'date-time',
          },
        },
        required: ['customer_id', 'event_id', 'event_name'],
      },
    },
  },
  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.usageEvents.ingest(body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/discounts/list-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: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/discounts',
  operationId: 'list_discounts_handler',
};

export const tool: Tool = {
  name: 'list_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\nGET /discounts\n\n# Response Schema\n```json\n{\n  type: 'object',\n  properties: {\n    items: {\n      type: 'array',\n      description: 'Array of active (non-deleted) discounts for the current page.',\n      items: {\n        $ref: '#/$defs/discount'\n      }\n    }\n  },\n  required: [    'items'\n  ],\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: {
      page_number: {
        type: 'integer',
        description: 'Page number (default = 0).',
      },
      page_size: {
        type: 'integer',
        description: 'Page size (default = 10, max = 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.discounts.list(body).asResponse();
  return asTextContentResult(await maybeFilter(jq_filter, await response.json()));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/disputes/retrieve-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/{dispute_id}',
  operationId: 'get_dispute_handler',
};

export const tool: Tool = {
  name: 'retrieve_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  $ref: '#/$defs/get_dispute',\n  $defs: {\n    get_dispute: {\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        customer: {\n          $ref: '#/$defs/customer_limited_details'\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        reason: {\n          type: 'string',\n          description: 'Reason for the dispute'\n        },\n        remarks: {\n          type: 'string',\n          description: 'Remarks'\n        }\n      },\n      required: [        'amount',\n        'business_id',\n        'created_at',\n        'currency',\n        'customer',\n        'dispute_id',\n        'dispute_stage',\n        'dispute_status',\n        'payment_id'\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    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: {
      dispute_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: ['dispute_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

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

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/resources/refunds.ts:
--------------------------------------------------------------------------------

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

import { APIResource } from '../core/resource';
import * as MiscAPI from './misc';
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 Refunds extends APIResource {
  create(body: RefundCreateParams, options?: RequestOptions): APIPromise<Refund> {
    return this._client.post('/refunds', { body, ...options });
  }

  retrieve(refundID: string, options?: RequestOptions): APIPromise<Refund> {
    return this._client.get(path`/refunds/${refundID}`, options);
  }

  list(
    query: RefundListParams | null | undefined = {},
    options?: RequestOptions,
  ): PagePromise<RefundListResponsesDefaultPageNumberPagination, RefundListResponse> {
    return this._client.getAPIList('/refunds', DefaultPageNumberPagination<RefundListResponse>, {
      query,
      ...options,
    });
  }
}

export type RefundListResponsesDefaultPageNumberPagination = DefaultPageNumberPagination<RefundListResponse>;

export interface Refund {
  /**
   * The unique identifier of the business issuing the refund.
   */
  business_id: string;

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

  /**
   * Details about the customer for this refund (from the associated payment)
   */
  customer: PaymentsAPI.CustomerLimitedDetails;

  /**
   * If true the refund is a partial refund
   */
  is_partial: boolean;

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

  /**
   * The unique identifier of the refund.
   */
  refund_id: string;

  /**
   * The current status of the refund.
   */
  status: RefundStatus;

  /**
   * The refunded amount.
   */
  amount?: number | null;

  /**
   * The currency of the refund, represented as an ISO 4217 currency code.
   */
  currency?: MiscAPI.Currency | null;

  /**
   * The reason provided for the refund, if any. Optional.
   */
  reason?: string | null;
}

export type RefundStatus = 'succeeded' | 'failed' | 'pending' | 'review';

export interface RefundListResponse {
  /**
   * The unique identifier of the business issuing the refund.
   */
  business_id: string;

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

  /**
   * If true the refund is a partial refund
   */
  is_partial: boolean;

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

  /**
   * The unique identifier of the refund.
   */
  refund_id: string;

  /**
   * The current status of the refund.
   */
  status: RefundStatus;

  /**
   * The refunded amount.
   */
  amount?: number | null;

  /**
   * The currency of the refund, represented as an ISO 4217 currency code.
   */
  currency?: MiscAPI.Currency | null;

  /**
   * The reason provided for the refund, if any. Optional.
   */
  reason?: string | null;
}

export interface RefundCreateParams {
  /**
   * The unique identifier of the payment to be refunded.
   */
  payment_id: string;

  /**
   * Partially Refund an Individual Item
   */
  items?: Array<RefundCreateParams.Item> | null;

  /**
   * The reason for the refund, if any. Maximum length is 3000 characters. Optional.
   */
  reason?: string | null;
}

export namespace RefundCreateParams {
  export interface Item {
    /**
     * The id of the item (i.e. `product_id` or `addon_id`)
     */
    item_id: string;

    /**
     * The amount to refund. if None the whole item is refunded
     */
    amount?: number | null;

    /**
     * Specify if tax is inclusive of the refund. Default true.
     */
    tax_inclusive?: boolean;
  }
}

export interface RefundListParams 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 status
   */
  status?: 'succeeded' | 'failed' | 'pending' | 'review';
}

export declare namespace Refunds {
  export {
    type Refund as Refund,
    type RefundStatus as RefundStatus,
    type RefundListResponse as RefundListResponse,
    type RefundListResponsesDefaultPageNumberPagination as RefundListResponsesDefaultPageNumberPagination,
    type RefundCreateParams as RefundCreateParams,
    type RefundListParams as RefundListParams,
  };
}

```

--------------------------------------------------------------------------------
/src/resources/misc.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 { RequestOptions } from '../internal/request-options';

export class Misc extends APIResource {
  listSupportedCountries(options?: RequestOptions): APIPromise<MiscListSupportedCountriesResponse> {
    return this._client.get('/checkout/supported_countries', options);
  }
}

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

export type Currency =
  | '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';

/**
 * Represents the different categories of taxation applicable to various products
 * and services.
 */
export type TaxCategory = 'digital_products' | 'saas' | 'e_book' | 'edtech';

export type MiscListSupportedCountriesResponse = Array<CountryCode>;

export declare namespace Misc {
  export {
    type CountryCode as CountryCode,
    type Currency as Currency,
    type TaxCategory as TaxCategory,
    type MiscListSupportedCountriesResponse as MiscListSupportedCountriesResponse,
  };
}

```

--------------------------------------------------------------------------------
/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 };

```
Page 2/8FirstPrevNextLast